small refactor
This commit is contained in:
parent
bf84395efe
commit
44c330fca4
|
@ -6,9 +6,10 @@ NEXT_PUBLIC_SUPABASE_URL=
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
SUPABASE_SERVICE_ROLE_SECRET=
|
SUPABASE_SERVICE_ROLE_SECRET=
|
||||||
NEXT_PUBLIC_SUPABASE_STORAGE_URL=
|
NEXT_PUBLIC_SUPABASE_STORAGE_URL=
|
||||||
|
|
||||||
# Supabase Local URL
|
# Supabase Local URL
|
||||||
NEXT_PUBLIC_SUPABASE_URL=
|
# NEXT_PUBLIC_SUPABASE_URL=
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
# NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
|
|
||||||
# Supabase Service Role Secret Key
|
# Supabase Service Role Secret Key
|
||||||
SERVICE_ROLE_SECRET=
|
SERVICE_ROLE_SECRET=
|
||||||
|
@ -21,8 +22,6 @@ SEND_EMAIL_HOOK_SECRET=
|
||||||
# Connect to Supabase via connection pooling with Supavisor.
|
# Connect to Supabase via connection pooling with Supavisor.
|
||||||
DATABASE_URL=
|
DATABASE_URL=
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Direct connection to the database. Used for migrations.
|
# Direct connection to the database. Used for migrations.
|
||||||
DIRECT_URL=
|
DIRECT_URL=
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ export async function createUser(
|
||||||
|
|
||||||
const { data, error } = await supabase.auth.admin.createUser({
|
const { data, error } = await supabase.auth.admin.createUser({
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.encrypted_password,
|
password: params.password,
|
||||||
phone: params.phone,
|
phone: params.phone,
|
||||||
email_confirm: params.email_confirm,
|
email_confirm: params.email_confirm,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Input } from "../../../_components/ui/input";
|
||||||
import { SubmitButton } from "../../../_components/submit-button";
|
import { SubmitButton } from "../../../_components/submit-button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { FormField } from "../../../_components/form-field";
|
import { FormField } from "../../../_components/form-field";
|
||||||
import { useSignInForm } from "@/src/controller/auth/sign-in-controller";
|
import { useSignInForm } from "@/src/interface-adapters/controllers/auth/sign-in-controller";
|
||||||
|
|
||||||
export function SignInForm({
|
export function SignInForm({
|
||||||
className,
|
className,
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/app/_components/ui/card";
|
} from "@/app/_components/ui/card";
|
||||||
import { cn } from "@/app/_lib/utils";
|
import { cn } from "@/app/_lib/utils";
|
||||||
import { useVerifyOtpForm } from "@/src/controller/auth/verify-otp.controller";
|
import { useVerifyOtpForm } from "@/src/interface-adapters/controllers/auth/verify-otp.controller";
|
||||||
|
|
||||||
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ export type Profile = z.infer<typeof ProfileSchema>;
|
||||||
|
|
||||||
export const CreateUserParamsSchema = z.object({
|
export const CreateUserParamsSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
encrypted_password: z.string(),
|
password: z.string(),
|
||||||
phone: z.string().optional(),
|
phone: z.string().optional(),
|
||||||
user_metadata: z.record(z.any()).optional(),
|
user_metadata: z.record(z.any()).optional(),
|
||||||
email_confirm: z.boolean().optional(),
|
email_confirm: z.boolean().optional(),
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
import { createAdminClient } from "@/app/_utils/supabase/admin";
|
||||||
|
import { createClient } from "@/app/_utils/supabase/client";
|
||||||
|
import { CreateUserParams, InviteUserParams, UpdateUserParams, User, UserResponse } from "@/src/entities/models/users/users.model";
|
||||||
|
import db from "@/prisma/db";
|
||||||
|
import { DatabaseOperationError, NotFoundError, InputParseError, AuthenticationError, UnauthenticatedError, UnauthorizedError } from "@/path/to/custom/errors";
|
||||||
|
|
||||||
|
export class UsersRepository {
|
||||||
|
private supabaseAdmin = createAdminClient();
|
||||||
|
private supabaseClient = createClient();
|
||||||
|
|
||||||
|
async fetchUsers(): Promise<User[]> {
|
||||||
|
const users = await db.users.findMany({
|
||||||
|
include: {
|
||||||
|
profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!users) {
|
||||||
|
throw new NotFoundError("Users not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentUser(): Promise<UserResponse> {
|
||||||
|
const supabase = await this.supabaseClient;
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { user },
|
||||||
|
error,
|
||||||
|
} = await supabase.auth.getUser();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error fetching current user:", error);
|
||||||
|
throw new AuthenticationError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userDetail = await db.users.findUnique({
|
||||||
|
where: {
|
||||||
|
id: user?.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userDetail) {
|
||||||
|
throw new NotFoundError("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
user: userDetail,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(params: CreateUserParams): Promise<UserResponse> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.admin.createUser({
|
||||||
|
email: params.email,
|
||||||
|
password: params.password,
|
||||||
|
phone: params.phone,
|
||||||
|
email_confirm: params.email_confirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error creating user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
user: data.user,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadAvatar(userId: string, email: string, file: File) {
|
||||||
|
try {
|
||||||
|
const supabase = await this.supabaseClient;
|
||||||
|
|
||||||
|
const fileExt = file.name.split(".").pop();
|
||||||
|
const emailName = email.split("@")[0];
|
||||||
|
const fileName = `AVR-${emailName}.${fileExt}`;
|
||||||
|
|
||||||
|
const filePath = `${userId}/${fileName}`;
|
||||||
|
|
||||||
|
const { error: uploadError } = await supabase.storage
|
||||||
|
.from("avatars")
|
||||||
|
.upload(filePath, file, {
|
||||||
|
upsert: true,
|
||||||
|
contentType: file.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uploadError) {
|
||||||
|
console.error("Error uploading avatar:", uploadError);
|
||||||
|
throw new DatabaseOperationError(uploadError.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { publicUrl },
|
||||||
|
} = supabase.storage.from("avatars").getPublicUrl(filePath);
|
||||||
|
|
||||||
|
await db.users.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
profile: {
|
||||||
|
update: {
|
||||||
|
avatar: publicUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return publicUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error uploading avatar:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateUser(userId: string, params: UpdateUserParams): Promise<UserResponse> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
|
email: params.email,
|
||||||
|
email_confirm: params.email_confirmed_at,
|
||||||
|
password: params.encrypted_password ?? undefined,
|
||||||
|
password_hash: params.encrypted_password ?? undefined,
|
||||||
|
phone: params.phone,
|
||||||
|
phone_confirm: params.phone_confirmed_at,
|
||||||
|
role: params.role,
|
||||||
|
user_metadata: params.user_metadata,
|
||||||
|
app_metadata: params.app_metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error updating user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db.users.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUser = await db.users.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
role: params.role || user.role,
|
||||||
|
invited_at: params.invited_at || user.invited_at,
|
||||||
|
confirmed_at: params.confirmed_at || user.confirmed_at,
|
||||||
|
last_sign_in_at: params.last_sign_in_at || user.last_sign_in_at,
|
||||||
|
is_anonymous: params.is_anonymous || user.is_anonymous,
|
||||||
|
created_at: params.created_at || user.created_at,
|
||||||
|
updated_at: params.updated_at || user.updated_at,
|
||||||
|
profile: {
|
||||||
|
update: {
|
||||||
|
avatar: params.profile?.avatar || user.profile?.avatar,
|
||||||
|
username: params.profile?.username || user.profile?.username,
|
||||||
|
first_name: params.profile?.first_name || user.profile?.first_name,
|
||||||
|
last_name: params.profile?.last_name || user.profile?.last_name,
|
||||||
|
bio: params.profile?.bio || user.profile?.bio,
|
||||||
|
address: params.profile?.address || user.profile?.address,
|
||||||
|
birth_date: params.profile?.birth_date || user.profile?.birth_date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
...data.user,
|
||||||
|
role: params.role,
|
||||||
|
profile: {
|
||||||
|
user_id: userId,
|
||||||
|
...updateUser.profile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(userId: string): Promise<void> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.admin.deleteUser(userId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error deleting user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendPasswordRecovery(email: string): Promise<void> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||||
|
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error sending password recovery:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMagicLink(email: string): Promise<void> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.signInWithOtp({
|
||||||
|
email,
|
||||||
|
options: {
|
||||||
|
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error sending magic link:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async banUser(userId: string): Promise<UserResponse> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const banUntil = new Date();
|
||||||
|
banUntil.setFullYear(banUntil.getFullYear() + 100);
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
|
ban_duration: "100h",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error banning user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
user: data.user,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async unbanUser(userId: string): Promise<UserResponse> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
|
ban_duration: "none",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error unbanning user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db.users.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
banned_until: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
user: data.user,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async inviteUser(params: InviteUserParams): Promise<void> {
|
||||||
|
const supabase = this.supabaseAdmin;
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.admin.inviteUserByEmail(params.email, {
|
||||||
|
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/api/auth/callback`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error inviting user:", error);
|
||||||
|
throw new DatabaseOperationError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue