import { FlowExecCtx } from "@/core/flow.execution.context"; import { ERROR_CODES, type Err } from "@pkg/result"; import { getError, logger } from "@pkg/logger"; import { auth } from "../auth/config.base"; import { account } from "@pkg/db/schema"; import { ResultAsync } from "neverthrow"; import { Database, eq } from "@pkg/db"; import { nanoid } from "nanoid"; export class AccountRepository { constructor(private db: Database) {} private dbError(fctx: FlowExecCtx, detail: string): Err { return getError({ flowId: fctx.flowId, code: ERROR_CODES.DATABASE_ERROR, message: "Database operation failed", description: "Please try again later", detail, }); } private accountNotFound(fctx: FlowExecCtx): Err { return getError({ flowId: fctx.flowId, code: ERROR_CODES.NOT_FOUND, message: "Account not found", description: "Please try again later", detail: "Account not found for user", }); } ensureAccountExists( fctx: FlowExecCtx, userId: string, ): ResultAsync { logger.info("Checking if account exists for user", { ...fctx, userId, }); return ResultAsync.fromPromise( this.db.query.account.findFirst({ where: eq(account.userId, userId), }), (error) => { logger.error("Failed to check account existence", { ...fctx, error, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((existingAccount) => { if (existingAccount) { logger.info("Account already exists for user", { ...fctx, userId, }); return ResultAsync.fromSafePromise(Promise.resolve(true)); } logger.info( "Account does not exist, creating new account for user", { ...fctx, userId, }, ); return ResultAsync.fromPromise( auth.$context.then((ctx) => ctx.password.hash(nanoid())), (error) => { logger.error("Failed to hash password", { ...fctx, error, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((password) => { const aid = nanoid(); return ResultAsync.fromPromise( this.db .insert(account) .values({ id: aid, accountId: userId, providerId: "credential", userId: userId, password, createdAt: new Date(), updatedAt: new Date(), }) .execute(), (error) => { logger.error("Failed to create account", { ...fctx, error, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).map(() => { logger.info("Account created successfully for user", { ...fctx, userId, }); return false; }); }); }); } rotatePassword( fctx: FlowExecCtx, userId: string, password: string, ): ResultAsync { logger.info("Starting password rotation for user", { ...fctx, userId, }); return ResultAsync.fromPromise( this.db.query.account.findFirst({ where: eq(account.userId, userId), }), (error) => { logger.error( "Failed to check account existence for password rotation", { ...fctx, error, }, ); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((existingAccount) => { if (!existingAccount) { logger.error("Account not found for user", { ...fctx, userId, }); return ResultAsync.fromSafePromise( Promise.resolve(this.accountNotFound(fctx)), ).andThen((err) => ResultAsync.fromSafePromise(Promise.reject(err)), ); } return ResultAsync.fromPromise( auth.$context.then((ctx) => ctx.password.hash(password)), (error) => { logger.error("Failed to hash password for rotation", { ...fctx, error, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((hashed) => { logger.info("Updating user's password in database", { ...fctx, }); return ResultAsync.fromPromise( this.db .update(account) .set({ password: hashed }) .where(eq(account.userId, userId)) .returning() .execute(), (error) => { logger.error("Failed to update password", { ...fctx, error, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).map((result) => { logger.info("User's password updated successfully", { ...fctx, }); logger.debug("Password rotation result", { ...fctx, result, }); return password; }); }); }); } }