2025-08-25 01:48:34 +12:00

144 lines
3.5 KiB
TypeScript

import { UserType } from '@server/constants/user';
// getSettings import removed - not used in simplified Agregarr User entity
import bcrypt from 'bcrypt';
import { randomUUID } from 'crypto';
import { default as generatePassword } from 'secure-random-password';
import {
AfterLoad,
Column,
CreateDateColumn,
Entity,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { UserSettings } from './UserSettings';
@Entity()
export class User {
public static filterMany(
users: User[],
showFiltered?: boolean
): Partial<User>[] {
return users.map((u) => u.filter(showFiltered));
}
static readonly filteredFields: string[] = ['email', 'plexId'];
public displayName: string;
@PrimaryGeneratedColumn()
public id: number;
@Column({
unique: true,
transformer: {
from: (value: string): string => (value ?? '').toLowerCase(),
to: (value: string): string => (value ?? '').toLowerCase(),
},
})
public email: string;
@Column({ nullable: true })
public plexUsername?: string;
@Column({ nullable: true })
public plexTitle?: string;
@Column({ type: 'boolean', default: false })
public hasPlexPass = false;
@Column({ nullable: true })
public username?: string;
@Column({ nullable: true, select: false })
public password?: string;
@Column({ nullable: true, select: false })
public resetPasswordGuid?: string;
@Column({ type: 'date', nullable: true })
public recoveryLinkExpirationDate?: Date | null;
@Column({ type: 'integer', default: UserType.PLEX })
public userType: UserType;
@Column({ nullable: true, select: true })
public plexId?: number;
@Column({ nullable: true })
public externalOverseerrId?: number;
@Column({ nullable: true, select: false })
public plexToken?: string;
@Column({ type: 'integer', default: 0 })
public permissions = 0;
@Column()
public avatar: string;
@OneToOne(() => UserSettings, (settings) => settings.user, {
cascade: true,
eager: true,
onDelete: 'CASCADE',
})
public settings?: UserSettings;
@CreateDateColumn()
public createdAt: Date;
@UpdateDateColumn()
public updatedAt: Date;
constructor(init?: Partial<User>) {
Object.assign(this, init);
}
public filter(showFiltered?: boolean): Partial<User> {
const filtered: Partial<User> = Object.assign(
{},
...(Object.keys(this) as (keyof User)[])
.filter((k) => showFiltered || !User.filteredFields.includes(k))
.map((k) => ({ [k]: this[k] }))
);
return filtered;
}
public passwordMatch(password: string): Promise<boolean> {
return new Promise((resolve) => {
if (this.password) {
resolve(bcrypt.compare(password, this.password));
} else {
return resolve(false);
}
});
}
public async setPassword(password: string): Promise<void> {
const hashedPassword = await bcrypt.hash(password, 12);
this.password = hashedPassword;
}
public async generatePassword(): Promise<void> {
const password = generatePassword.randomPassword({ length: 16 });
this.setPassword(password);
}
public async resetPassword(): Promise<void> {
const guid = randomUUID();
this.resetPasswordGuid = guid;
// 24 hours into the future
const targetDate = new Date();
targetDate.setDate(targetDate.getDate() + 1);
this.recoveryLinkExpirationDate = targetDate;
}
@AfterLoad()
public setDisplayName(): void {
this.displayName = this.username || this.plexUsername || this.email;
}
}