mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 09:13:13 +03:00
chore(server,cli,web): housekeeping and stricter code style (#6751)
* add unicorn to eslint * fix lint errors for cli * fix merge * fix album name extraction * Update cli/src/commands/upload.command.ts Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> * es2k23 * use lowercase os * return undefined album name * fix bug in asset response dto * auto fix issues * fix server code style * es2022 and formatting * fix compilation error * fix test * fix config load * fix last lint errors * set string type * bump ts * start work on web * web formatting * Fix UUIDParamDto as UUIDParamDto * fix library service lint * fix web errors * fix errors * formatting * wip * lints fixed * web can now start * alphabetical package json * rename error * chore: clean up --------- Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e4d0560d49
commit
f44fa45aa0
@@ -6,7 +6,7 @@ module.exports = {
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:unicorn/recommended'],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
@@ -19,6 +19,14 @@ module.exports = {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'unicorn/prefer-module': 'off',
|
||||
curly: 2,
|
||||
'prettier/prettier': 0,
|
||||
'unicorn/prevent-abbreviations': [
|
||||
'error',
|
||||
{
|
||||
ignore: ['\\.e2e-spec$', /^ignore/i],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
30
cli/package-lock.json
generated
30
cli/package-lock.json
generated
@@ -30,15 +30,15 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitest/coverage-v8": "^1.2.2",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^50.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^50.0.1",
|
||||
"immich": "file:../server",
|
||||
"mock-fs": "^5.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.5.3",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.2.1"
|
||||
}
|
||||
},
|
||||
@@ -73,6 +73,7 @@
|
||||
"@nestjs/typeorm": "^10.0.0",
|
||||
"@nestjs/websockets": "^10.2.2",
|
||||
"@socket.io/postgres-adapter": "^0.3.1",
|
||||
"@types/picomatch": "^2.3.3",
|
||||
"archiver": "^6.0.0",
|
||||
"async-lock": "^1.4.0",
|
||||
"axios": "^1.5.0",
|
||||
@@ -96,6 +97,7 @@
|
||||
"node-addon-api": "^7.0.0",
|
||||
"openid-client": "^5.4.3",
|
||||
"pg": "^8.11.3",
|
||||
"picomatch": "^3.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
@@ -127,10 +129,12 @@
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.4.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^50.0.1",
|
||||
"jest": "^29.6.4",
|
||||
"jest-when": "^3.6.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
@@ -8768,6 +8772,7 @@
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^20.5.7",
|
||||
"@types/picomatch": "^2.3.3",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
@@ -8778,13 +8783,15 @@
|
||||
"axios": "^1.5.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^4.8.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^50.0.1",
|
||||
"exiftool-vendored": "~24.4.0",
|
||||
"exiftool-vendored.pl": "12.73",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
@@ -8803,6 +8810,7 @@
|
||||
"node-addon-api": "^7.0.0",
|
||||
"openid-client": "^5.4.3",
|
||||
"pg": "^8.11.3",
|
||||
"picomatch": "^3.0.1",
|
||||
"prettier": "^3.0.2",
|
||||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
||||
@@ -28,18 +28,18 @@
|
||||
"@types/cli-progress": "^3.11.0",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^20.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.4.1",
|
||||
"@vitest/coverage-v8": "^1.2.2",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^50.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^50.0.1",
|
||||
"immich": "file:../server",
|
||||
"mock-fs": "^5.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.5.3",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseCommand } from './base-command';
|
||||
import { basename } from 'node:path';
|
||||
import { access, constants, stat, unlink } from 'node:fs/promises';
|
||||
import { createHash } from 'node:crypto';
|
||||
import Os from 'os';
|
||||
import os from 'node:os';
|
||||
|
||||
class Asset {
|
||||
readonly path: string;
|
||||
@@ -27,7 +27,7 @@ class Asset {
|
||||
|
||||
async prepare() {
|
||||
const stats = await stat(this.path);
|
||||
this.deviceAssetId = `${basename(this.path)}-${stats.size}`.replace(/\s+/g, '');
|
||||
this.deviceAssetId = `${basename(this.path)}-${stats.size}`.replaceAll(/\s+/g, '');
|
||||
this.fileCreatedAt = stats.mtime.toISOString();
|
||||
this.fileModifiedAt = stats.mtime.toISOString();
|
||||
this.fileSize = stats.size;
|
||||
@@ -35,9 +35,15 @@ class Asset {
|
||||
}
|
||||
|
||||
async getUploadFormData(): Promise<FormData> {
|
||||
if (!this.deviceAssetId) throw new Error('Device asset id not set');
|
||||
if (!this.fileCreatedAt) throw new Error('File created at not set');
|
||||
if (!this.fileModifiedAt) throw new Error('File modified at not set');
|
||||
if (!this.deviceAssetId) {
|
||||
throw new Error('Device asset id not set');
|
||||
}
|
||||
if (!this.fileCreatedAt) {
|
||||
throw new Error('File created at not set');
|
||||
}
|
||||
if (!this.fileModifiedAt) {
|
||||
throw new Error('File modified at not set');
|
||||
}
|
||||
|
||||
// TODO: doesn't xmp replace the file extension? Will need investigation
|
||||
const sideCarPath = `${this.path}.xmp`;
|
||||
@@ -45,7 +51,7 @@ class Asset {
|
||||
try {
|
||||
await access(sideCarPath, constants.R_OK);
|
||||
sidecarData = createReadStream(sideCarPath);
|
||||
} catch (error) {}
|
||||
} catch {}
|
||||
|
||||
const data: any = {
|
||||
assetData: createReadStream(this.path),
|
||||
@@ -57,8 +63,8 @@ class Asset {
|
||||
};
|
||||
const formData = new FormData();
|
||||
|
||||
for (const prop in data) {
|
||||
formData.append(prop, data[prop]);
|
||||
for (const property in data) {
|
||||
formData.append(property, data[property]);
|
||||
}
|
||||
|
||||
if (sidecarData) {
|
||||
@@ -86,12 +92,8 @@ class Asset {
|
||||
return await sha1(this.path);
|
||||
}
|
||||
|
||||
private extractAlbumName(): string {
|
||||
if (Os.platform() === 'win32') {
|
||||
return this.path.split('\\').slice(-2)[0];
|
||||
} else {
|
||||
return this.path.split('/').slice(-2)[0];
|
||||
}
|
||||
private extractAlbumName(): string | undefined {
|
||||
return os.platform() === 'win32' ? this.path.split('\\').at(-2) : this.path.split('/').at(-2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ export class UploadCommand extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const existingAlbums = (await this.immichApi.albumApi.getAllAlbums()).data;
|
||||
const { data: existingAlbums } = await this.immichApi.albumApi.getAllAlbums();
|
||||
|
||||
uploadProgress.start(totalSize, 0);
|
||||
uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
|
||||
@@ -195,32 +197,30 @@ export class UploadCommand extends BaseCommand {
|
||||
skipAsset = skipUpload && !isDuplicate;
|
||||
}
|
||||
|
||||
if (!skipAsset) {
|
||||
if (!options.dryRun) {
|
||||
if (!skipUpload) {
|
||||
const formData = await asset.getUploadFormData();
|
||||
const res = await this.uploadAsset(formData);
|
||||
existingAssetId = res.data.id;
|
||||
uploadCounter++;
|
||||
totalSizeUploaded += asset.fileSize;
|
||||
if (!skipAsset && !options.dryRun) {
|
||||
if (!skipUpload) {
|
||||
const formData = await asset.getUploadFormData();
|
||||
const { data } = await this.uploadAsset(formData);
|
||||
existingAssetId = data.id;
|
||||
uploadCounter++;
|
||||
totalSizeUploaded += asset.fileSize;
|
||||
}
|
||||
|
||||
if ((options.album || options.albumName) && asset.albumName !== undefined) {
|
||||
let album = existingAlbums.find((album) => album.albumName === asset.albumName);
|
||||
if (!album) {
|
||||
const { data } = await this.immichApi.albumApi.createAlbum({
|
||||
createAlbumDto: { albumName: asset.albumName },
|
||||
});
|
||||
album = data;
|
||||
existingAlbums.push(album);
|
||||
}
|
||||
|
||||
if ((options.album || options.albumName) && asset.albumName !== undefined) {
|
||||
let album = existingAlbums.find((album) => album.albumName === asset.albumName);
|
||||
if (!album) {
|
||||
const res = await this.immichApi.albumApi.createAlbum({
|
||||
createAlbumDto: { albumName: asset.albumName },
|
||||
});
|
||||
album = res.data;
|
||||
existingAlbums.push(album);
|
||||
}
|
||||
|
||||
if (existingAssetId) {
|
||||
await this.immichApi.albumApi.addAssetsToAlbum({
|
||||
id: album.id,
|
||||
bulkIdsDto: { ids: [existingAssetId] },
|
||||
});
|
||||
}
|
||||
if (existingAssetId) {
|
||||
await this.immichApi.albumApi.addAssetsToAlbum({
|
||||
id: album.id,
|
||||
bulkIdsDto: { ids: [existingAssetId] },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,12 +233,7 @@ export class UploadCommand extends BaseCommand {
|
||||
uploadProgress.stop();
|
||||
}
|
||||
|
||||
let messageStart;
|
||||
if (options.dryRun) {
|
||||
messageStart = 'Would have';
|
||||
} else {
|
||||
messageStart = 'Successfully';
|
||||
}
|
||||
const messageStart = options.dryRun ? 'Would have' : 'Successfully';
|
||||
|
||||
if (uploadCounter === 0) {
|
||||
console.log('All assets were already uploaded, nothing to do.');
|
||||
@@ -276,12 +271,11 @@ export class UploadCommand extends BaseCommand {
|
||||
'x-api-key': this.immichApi.apiKey,
|
||||
...data.getHeaders(),
|
||||
},
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity,
|
||||
maxContentLength: Number.POSITIVE_INFINITY,
|
||||
maxBodyLength: Number.POSITIVE_INFINITY,
|
||||
data,
|
||||
};
|
||||
|
||||
const res = await axios(config);
|
||||
return res;
|
||||
return axios(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
#! /usr/bin/env node
|
||||
import { Command, Option } from 'commander';
|
||||
import path from 'node:path';
|
||||
import os from 'os';
|
||||
import os from 'node:os';
|
||||
import { version } from '../package.json';
|
||||
import { LoginCommand } from './commands/login';
|
||||
import { LogoutCommand } from './commands/logout.command';
|
||||
import { ServerInfoCommand } from './commands/server-info.command';
|
||||
import { UploadCommand } from './commands/upload.command';
|
||||
|
||||
const userHomeDir = os.homedir();
|
||||
const configDir = path.join(userHomeDir, '.config/immich/');
|
||||
const homeDirectory = os.homedir();
|
||||
const configDirectory = path.join(homeDirectory, '.config/immich/');
|
||||
|
||||
const program = new Command()
|
||||
.name('immich')
|
||||
.version(version)
|
||||
.description('Command line interface for Immich')
|
||||
.addOption(new Option('-d, --config', 'Configuration directory').env('IMMICH_CONFIG_DIR').default(configDir));
|
||||
.addOption(new Option('-d, --config', 'Configuration directory').env('IMMICH_CONFIG_DIR').default(configDirectory));
|
||||
|
||||
program
|
||||
.command('upload')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { glob } from 'glob';
|
||||
import * as fs from 'fs';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
export class CrawlOptions {
|
||||
pathsToCrawl!: string[];
|
||||
@@ -12,14 +12,14 @@ export class CrawlService {
|
||||
private readonly extensions!: string[];
|
||||
|
||||
constructor(image: string[], video: string[]) {
|
||||
this.extensions = image.concat(video).map((extension) => extension.replace('.', ''));
|
||||
this.extensions = [...image, ...video].map((extension) => extension.replace('.', ''));
|
||||
}
|
||||
|
||||
async crawl(options: CrawlOptions): Promise<string[]> {
|
||||
const { recursive, pathsToCrawl, exclusionPatterns, includeHidden } = options;
|
||||
|
||||
if (!pathsToCrawl) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
const patterns: string[] = [];
|
||||
@@ -65,8 +65,6 @@ export class CrawlService {
|
||||
ignore: exclusionPatterns,
|
||||
});
|
||||
|
||||
const returnedFiles = crawledFiles.concat(globbedFiles);
|
||||
returnedFiles.sort();
|
||||
return returnedFiles;
|
||||
return [...crawledFiles, ...globbedFiles].sort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { access, constants, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import yaml from 'yaml';
|
||||
@@ -15,12 +15,12 @@ class LoginError extends Error {
|
||||
}
|
||||
|
||||
export class SessionService {
|
||||
readonly configDir!: string;
|
||||
readonly configDirectory!: string;
|
||||
readonly authPath!: string;
|
||||
|
||||
constructor(configDir: string) {
|
||||
this.configDir = configDir;
|
||||
this.authPath = path.join(configDir, '/auth.yml');
|
||||
constructor(configDirectory: string) {
|
||||
this.configDirectory = configDirectory;
|
||||
this.authPath = path.join(configDirectory, '/auth.yml');
|
||||
}
|
||||
|
||||
async connect(): Promise<ImmichApi> {
|
||||
@@ -74,11 +74,11 @@ export class SessionService {
|
||||
|
||||
console.log(`Logged in as ${userInfo.email}`);
|
||||
|
||||
if (!existsSync(this.configDir)) {
|
||||
if (!existsSync(this.configDirectory)) {
|
||||
// Create config folder if it doesn't exist
|
||||
const created = await mkdir(this.configDir, { recursive: true });
|
||||
const created = await mkdir(this.configDirectory, { recursive: true });
|
||||
if (!created) {
|
||||
throw new Error(`Failed to create config folder ${this.configDir}`);
|
||||
throw new Error(`Failed to create config folder ${this.configDirectory}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg from '../package.json';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export interface ICliVersion {
|
||||
major: number;
|
||||
@@ -23,7 +23,7 @@ export class CliVersion implements ICliVersion {
|
||||
}
|
||||
|
||||
static fromString(version: string): CliVersion {
|
||||
const regex = /(?:v)?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/i;
|
||||
const regex = /v?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/i;
|
||||
const matchResult = version.match(regex);
|
||||
if (matchResult) {
|
||||
const [, major, minor, patch] = matchResult.map(Number);
|
||||
@@ -34,4 +34,4 @@ export class CliVersion implements ICliVersion {
|
||||
}
|
||||
}
|
||||
|
||||
export const cliVersion = CliVersion.fromString(pkg.version);
|
||||
export const cliVersion = CliVersion.fromString(version);
|
||||
|
||||
@@ -10,10 +10,10 @@ describe(`login-key (e2e)`, () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await testApp.create();
|
||||
if (!process.env.IMMICH_INSTANCE_URL) {
|
||||
throw new Error('IMMICH_INSTANCE_URL environment variable not set');
|
||||
} else {
|
||||
if (process.env.IMMICH_INSTANCE_URL) {
|
||||
instanceUrl = process.env.IMMICH_INSTANCE_URL;
|
||||
} else {
|
||||
throw new Error('IMMICH_INSTANCE_URL environment variable not set');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import path from 'path';
|
||||
import path from 'node:path';
|
||||
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
||||
import { access } from 'fs/promises';
|
||||
import { access } from 'node:fs/promises';
|
||||
|
||||
export const directoryExists = (directory: string) =>
|
||||
access(directory)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
export default async () => {
|
||||
let IMMICH_TEST_ASSET_PATH: string = '';
|
||||
@@ -12,11 +17,6 @@ export default async () => {
|
||||
IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH;
|
||||
}
|
||||
|
||||
const directoryExists = async (dirPath: string) =>
|
||||
await access(dirPath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!(await directoryExists(`${IMMICH_TEST_ASSET_PATH}/albums`))) {
|
||||
throw new Error(
|
||||
`Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${IMMICH_TEST_ASSET_PATH} before testing`,
|
||||
|
||||
@@ -17,6 +17,6 @@ export default defineConfig({
|
||||
minForks: 1,
|
||||
},
|
||||
},
|
||||
testTimeout: 10000,
|
||||
testTimeout: 10_000,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "es2021",
|
||||
"target": "es2022",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
@@ -30,5 +30,5 @@
|
||||
},
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"exclude": ["dist", "node_modules", "upload"]
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user