491 lines
17 KiB
TypeScript
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;
|
|
}
|
|
})
|
|
}
|
|
} |