import { FlowExecCtx } from "@/core/flow.execution.context"; import { traceResultAsync } from "@core/observability"; import { ERROR_CODES, type Err } from "@pkg/result"; import { getError, logDomainEvent } from "@pkg/logger"; import { auth } from "../auth/config.base"; import { account } from "@pkg/db/schema"; import { ResultAsync, errAsync, okAsync } 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 { return traceResultAsync({ name: "logic.user.repository.ensureAccountExists", fctx, attributes: { "app.user.id": userId }, fn: () => { const startedAt = Date.now(); logDomainEvent({ event: "account.ensure_exists.started", fctx, meta: { userId }, }); return ResultAsync.fromPromise( this.db.query.account.findFirst({ where: eq(account.userId, userId), }), (error) => { logDomainEvent({ level: "error", event: "account.ensure_exists.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId }, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((existingAccount) => { if (existingAccount) { logDomainEvent({ event: "account.ensure_exists.succeeded", fctx, durationMs: Date.now() - startedAt, meta: { userId, existed: true }, }); return okAsync(true); } return ResultAsync.fromPromise( auth.$context.then((ctx) => ctx.password.hash(nanoid())), (error) => { logDomainEvent({ level: "error", event: "account.ensure_exists.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId, stage: "hash_password" }, }); 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, password, createdAt: new Date(), updatedAt: new Date(), }) .execute(), (error) => { logDomainEvent({ level: "error", event: "account.ensure_exists.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId, stage: "create_account" }, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).map(() => { logDomainEvent({ event: "account.ensure_exists.succeeded", fctx, durationMs: Date.now() - startedAt, meta: { userId, existed: false }, }); return false; }); }); }); }, }); } rotatePassword( fctx: FlowExecCtx, userId: string, password: string, ): ResultAsync { return traceResultAsync({ name: "logic.user.repository.rotatePassword", fctx, attributes: { "app.user.id": userId }, fn: () => { const startedAt = Date.now(); logDomainEvent({ event: "account.rotate_password.started", fctx, meta: { userId }, }); return ResultAsync.fromPromise( this.db.query.account.findFirst({ where: eq(account.userId, userId), }), (error) => { logDomainEvent({ level: "error", event: "account.rotate_password.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId, stage: "check_exists" }, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((existingAccount) => { if (!existingAccount) { logDomainEvent({ level: "warn", event: "account.rotate_password.failed", fctx, durationMs: Date.now() - startedAt, error: { code: "NOT_FOUND", message: "Account not found" }, meta: { userId }, }); return errAsync(this.accountNotFound(fctx)); } return ResultAsync.fromPromise( auth.$context.then((ctx) => ctx.password.hash(password)), (error) => { logDomainEvent({ level: "error", event: "account.rotate_password.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId, stage: "hash_password" }, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).andThen((hashed) => { return ResultAsync.fromPromise( this.db .update(account) .set({ password: hashed }) .where(eq(account.userId, userId)) .returning() .execute(), (error) => { logDomainEvent({ level: "error", event: "account.rotate_password.failed", fctx, durationMs: Date.now() - startedAt, error, meta: { userId, stage: "update_password" }, }); return this.dbError( fctx, error instanceof Error ? error.message : String(error), ); }, ).map(() => { logDomainEvent({ event: "account.rotate_password.succeeded", fctx, durationMs: Date.now() - startedAt, meta: { userId }, }); return password; }); }); }); }, }); } }