import { Injectable } from '@nestjs/common'; import { isAbsolute } from 'node:path'; import { SALT_ROUNDS } from 'src/constants'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { BaseService } from 'src/services/base.service'; @Injectable() export class CliService extends BaseService { async listUsers(): Promise { const users = await this.userRepository.getList({ withDeleted: true }); return users.map((user) => mapUserAdmin(user)); } async resetAdminPassword(ask: (admin: UserAdminResponseDto) => Promise) { const admin = await this.userRepository.getAdmin(); if (!admin) { throw new Error('Admin account does not exist'); } const providedPassword = await ask(mapUserAdmin(admin)); const password = providedPassword || this.cryptoRepository.randomBytesAsText(24); const hashedPassword = await this.cryptoRepository.hashBcrypt(password, SALT_ROUNDS); await this.userRepository.update(admin.id, { password: hashedPassword }); return { admin, password, provided: !!providedPassword }; } async disablePasswordLogin(): Promise { const config = await this.getConfig({ withCache: false }); config.passwordLogin.enabled = false; await this.updateConfig(config); } async enablePasswordLogin(): Promise { const config = await this.getConfig({ withCache: false }); config.passwordLogin.enabled = true; await this.updateConfig(config); } async grantAdminAccess(email: string): Promise { const user = await this.userRepository.getByEmail(email); if (!user) { throw new Error('User does not exist'); } await this.userRepository.update(user.id, { isAdmin: true }); } async revokeAdminAccess(email: string): Promise { const user = await this.userRepository.getByEmail(email); if (!user) { throw new Error('User does not exist'); } await this.userRepository.update(user.id, { isAdmin: false }); } async disableOAuthLogin(): Promise { const config = await this.getConfig({ withCache: false }); config.oauth.enabled = false; await this.updateConfig(config); } async enableOAuthLogin(): Promise { const config = await this.getConfig({ withCache: false }); config.oauth.enabled = true; await this.updateConfig(config); } async getSampleFilePaths(): Promise { const [assets, people, users] = await Promise.all([ this.assetRepository.getFileSamples(), this.personRepository.getFileSamples(), this.userRepository.getFileSamples(), ]); const paths = []; for (const person of people) { paths.push(person.thumbnailPath); } for (const user of users) { paths.push(user.profileImagePath); } for (const asset of assets) { paths.push( asset.originalPath, asset.sidecarPath, asset.encodedVideoPath, ...asset.files.map((file) => file.path), ); } return paths.filter(Boolean) as string[]; } async migrateFilePaths({ oldValue, newValue, confirm, }: { oldValue: string; newValue: string; confirm: (data: { sourceFolder: string; targetFolder: string }) => Promise; }): Promise { let sourceFolder = oldValue; if (sourceFolder.startsWith('./')) { sourceFolder = sourceFolder.slice(2); } const targetFolder = newValue; if (!isAbsolute(targetFolder)) { throw new Error('Target media location must be an absolute path'); } if (!(await confirm({ sourceFolder, targetFolder }))) { return false; } await this.databaseRepository.migrateFilePaths(sourceFolder, targetFolder); return true; } cleanup() { return this.databaseRepository.shutdown(); } }