import { IUsersRepository } from "@/src/application/repositories/users.repository.interface"; import { ICrashReporterService } from "@/src/application/services/crash-reporter.service.interface"; import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; import { createAdminClient } from "@/app/_utils/supabase/admin"; import { createClient as createServerClient } from "@/app/_utils/supabase/server"; import { CreateUser, UpdateUser, User, UserResponse } from "@/src/entities/models/users/users.model"; import { ITransaction } from "@/src/entities/models/transaction.interface"; import db from "@/prisma/db"; import { DatabaseOperationError, NotFoundError } from "@/src/entities/errors/common"; import { AuthenticationError } from "@/src/entities/errors/auth"; export class UsersRepository implements IUsersRepository { constructor( private readonly instrumentationService: IInstrumentationService, private readonly crashReporterService: ICrashReporterService, private readonly supabaseAdmin = createAdminClient(), private readonly supabaseServer = createServerClient() ) { } async getUsers(): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > getUsers", }, async () => { try { const query = db.users.findMany({ include: { profile: true, }, }); const users = await this.instrumentationService.startSpan({ name: `UsersRepository > getUsers > Prisma: db.users.findMany`, op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await query; } ) if (!users) { throw new NotFoundError("Users not found"); } return users; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async getUserById(id: string): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > getUserById", }, async () => { try { const query = db.users.findUnique({ where: { id, }, include: { profile: true, }, }) const user = await this.instrumentationService.startSpan({ name: `UsersRepository > getUserById > Prisma: db.users.findUnique(${id})`, op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await query; } ) if (!user) { throw new NotFoundError("User not found"); } return { ...user, id, }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async getUserByUsername(username: string): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > getUserByUsername", }, async () => { try { const query = db.users.findFirst({ where: { profile: { username, }, }, include: { profile: true, }, }) const user = await this.instrumentationService.startSpan({ name: `UsersRepository > getUserByUsername > Prisma: db.users.findFirst(${username})`, op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await query; } ) if (!user) { throw new NotFoundError("User not found"); } return user; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async getUserByEmail(email: string): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > getUserByEmail", }, async () => { try { const query = db.users.findUnique({ where: { email, }, include: { profile: true, }, }) const user = await this.instrumentationService.startSpan({ name: `UsersRepository > getUserByEmail > Prisma: db.users.findUnique(${email})`, op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await query; } ) if (!user) { throw new NotFoundError("User not found"); } return user; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async getCurrentUser(): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > getCurrentUser", }, async () => { try { const supabase = await this.supabaseServer; const query = supabase.auth.getUser(); const { data, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > getCurrentUser > supabase.auth.getUser", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (error) { throw new AuthenticationError("Failed to get current user"); } if (!data) { throw new NotFoundError("User not found"); } return { ...data, id: data.user.id, }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async createUser(input: CreateUser, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > createUser", }, async () => { try { const supabase = this.supabaseAdmin; const query = supabase.auth.admin.createUser(input) const { data: { user } } = await this.instrumentationService.startSpan({ name: "UsersRepository > createUser > supabase.auth.admin.createUser", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (!user) { throw new DatabaseOperationError("Failed to create user"); } return user; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async inviteUser(email: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > inviteUser", }, async () => { try { const supabase = this.supabaseAdmin; const query = supabase.auth.admin.inviteUserByEmail(email); const { data: { user } } = await this.instrumentationService.startSpan({ name: "UsersRepository > inviteUser > supabase.auth.admin.inviteUserByEmail", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (!user) { throw new DatabaseOperationError("Failed to invite user"); } return user; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async updateUser(id: string, input: Partial, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > updateUser", }, async () => { try { const supabase = this.supabaseAdmin; const queryUpdateSupabaseUser = supabase.auth.admin.updateUserById(id, { email: input.email, email_confirm: input.email_confirmed_at, password: input.encrypted_password ?? undefined, password_hash: input.encrypted_password ?? undefined, phone: input.phone, phone_confirm: input.phone_confirmed_at, role: input.role, user_metadata: input.user_metadata, app_metadata: input.app_metadata, }); const { data, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > updateUser > supabase.auth.updateUser", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await queryUpdateSupabaseUser; } ) if (error) { throw new DatabaseOperationError("Failed to update user"); } const queryGetUser = db.users.findUnique({ where: { id, }, include: { profile: true, }, }) const user = await this.instrumentationService.startSpan({ name: "UsersRepository > updateUser > Prisma: db.users.update", op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await queryGetUser; } ) if (!user) { throw new NotFoundError("User not found"); } const queryUpdateUser = db.users.update({ where: { id, }, data: { role: input.role || user.role, invited_at: input.invited_at || user.invited_at, confirmed_at: input.confirmed_at || user.confirmed_at, last_sign_in_at: input.last_sign_in_at || user.last_sign_in_at, is_anonymous: input.is_anonymous || user.is_anonymous, created_at: input.created_at || user.created_at, updated_at: input.updated_at || user.updated_at, profile: { update: { avatar: input.profile?.avatar || user.profile?.avatar, username: input.profile?.username || user.profile?.username, first_name: input.profile?.first_name || user.profile?.first_name, last_name: input.profile?.last_name || user.profile?.last_name, bio: input.profile?.bio || user.profile?.bio, address: input.profile?.address || user.profile?.address, birth_date: input.profile?.birth_date || user.profile?.birth_date, }, }, }, include: { profile: true, }, }) const updatedUser = await this.instrumentationService.startSpan({ name: "UsersRepository > updateUser > Prisma: db.users.update", op: "db:query", attributes: { "system": "prisma" }, }, async () => { return await queryUpdateUser; } ) if (!updatedUser) { throw new DatabaseOperationError("Failed to update user"); } return { ...updatedUser, id, }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async deleteUser(id: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > deleteUser", }, async () => { try { const supabase = this.supabaseAdmin; const query = supabase.auth.admin.deleteUser(id); const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > deleteUser > supabase.auth.admin.deleteUser", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (error) { throw new DatabaseOperationError("Failed to delete user"); } return { ...user, id }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async banUser(id: string, ban_duration: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > banUser", }, async () => { try { const supabase = this.supabaseAdmin; const query = supabase.auth.admin.updateUserById(id, { ban_duration: ban_duration ?? "100h", }) const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > banUser > supabase.auth.admin.updateUserById", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (error) { throw new DatabaseOperationError("Failed to ban user"); } return { ...user, id }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } async unbanUser(id: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > unbanUser", }, async () => { try { const supabase = this.supabaseAdmin; const query = supabase.auth.admin.updateUserById(id, { ban_duration: "none", }) const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > unbanUser > supabase.auth.admin.updateUserById", op: "db:query", attributes: { "system": "supabase.auth" }, }, async () => { return await query; } ) if (error) { throw new DatabaseOperationError("Failed to unban user"); } return { ...user, id }; } catch (err) { this.crashReporterService.report(err); throw err; } }) } }