MIF_E31221222/sigap-website/src/infrastructure/repositories/users.repository.ts

491 lines
17 KiB
TypeScript

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<User[]> {
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<User | undefined> {
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<User | undefined> {
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<User | undefined> {
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<User> {
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<User> {
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<User> {
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<UpdateUser>, tx?: ITransaction): Promise<User> {
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<User> {
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<User> {
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<User> {
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;
}
})
}
}