import { Injectable } from "@nestjs/common"; import { and, eq, lte, sql } from "drizzle-orm"; import { DatabaseService } from "../../database/database.service"; import { contents, favorites, users } from "../../database/schemas"; @Injectable() export class UsersRepository { constructor(private readonly databaseService: DatabaseService) {} async create(data: { username: string; email: string; passwordHash: string; emailHash: string; }) { const [newUser] = await this.databaseService.db .insert(users) .values(data) .returning(); return newUser; } async findByEmailHash(emailHash: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, email: users.email, passwordHash: users.passwordHash, status: users.status, isTwoFactorEnabled: users.isTwoFactorEnabled, }) .from(users) .where(eq(users.emailHash, emailHash)) .limit(1); return result[0] || null; } async findOneWithPrivateData(uuid: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, email: users.email, displayName: users.displayName, avatarUrl: users.avatarUrl, bio: users.bio, status: users.status, isTwoFactorEnabled: users.isTwoFactorEnabled, createdAt: users.createdAt, updatedAt: users.updatedAt, }) .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0] || null; } async countAll() { const result = await this.databaseService.db .select({ count: sql`count(*)` }) .from(users); return Number(result[0].count); } async findAll(limit: number, offset: number) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, email: users.email, displayName: users.displayName, avatarUrl: users.avatarUrl, status: users.status, createdAt: users.createdAt, }) .from(users) .limit(limit) .offset(offset); return result; } async findByUsername(username: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, displayName: users.displayName, avatarUrl: users.avatarUrl, bio: users.bio, createdAt: users.createdAt, }) .from(users) .where(eq(users.username, username)) .limit(1); return result[0] || null; } async findOne(uuid: string) { const result = await this.databaseService.db .select() .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0] || null; } async update(uuid: string, data: Partial) { return await this.databaseService.db .update(users) .set({ ...data, updatedAt: new Date() }) .where(eq(users.uuid, uuid)) .returning(); } async getTwoFactorSecret(uuid: string) { const result = await this.databaseService.db .select({ secret: users.twoFactorSecret, }) .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0]?.secret || null; } async getUserContents(uuid: string) { return await this.databaseService.db .select() .from(contents) .where(eq(contents.userId, uuid)); } async getUserFavorites(uuid: string) { return await this.databaseService.db .select() .from(favorites) .where(eq(favorites.userId, uuid)); } async softDeleteUserAndContents(uuid: string) { return await this.databaseService.db.transaction(async (tx) => { const userResult = await tx .update(users) .set({ status: "deleted", deletedAt: new Date() }) .where(eq(users.uuid, uuid)) .returning(); await tx .update(contents) .set({ deletedAt: new Date() }) .where(eq(contents.userId, uuid)); return userResult; }); } async purgeDeleted(before: Date) { return await this.databaseService.db .delete(users) .where(and(eq(users.status, "deleted"), lte(users.deletedAt, before))) .returning(); } }