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:
Jonathan Jogenfors
2024-02-02 04:18:00 +01:00
committed by GitHub
parent e4d0560d49
commit f44fa45aa0
218 changed files with 2471 additions and 1244 deletions

View File

@ -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);
}
}

View File

@ -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')

View File

@ -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();
}
}

View File

@ -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}`);
}
}

View File

@ -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);