refactor project structure (backups)
This commit is contained in:
parent
5830eedb18
commit
693f5d265e
|
@ -6,10 +6,10 @@ export default async function DashboardPage() {
|
|||
const supabase = await createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!user) {
|
||||
if (!session) {
|
||||
return redirect("/sign-in");
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ export default async function DashboardPage() {
|
|||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div className="aspect-video rounded-xl bg-muted/50">
|
||||
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
|
||||
{JSON.stringify(user, null, 2)}
|
||||
{JSON.stringify(session, null, 2)}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -8,13 +8,33 @@ import { SubmitButton } from "@/app/_components/submit-button";
|
|||
import Link from "next/link";
|
||||
import { FormField } from "@/app/_components/form-field";
|
||||
import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in-controller";
|
||||
import { useState } from "react";
|
||||
import { signIn } from "../action";
|
||||
|
||||
export function SignInForm({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"form">) {
|
||||
|
||||
const { register, isPending, handleSubmit, errors } = useSignInController();
|
||||
const [error, setError] = useState<string>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (loading) return;
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
|
||||
setLoading(true);
|
||||
const res = await signIn(formData);
|
||||
if (res && res.error) {
|
||||
setError(res.error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
// const { register, isPending, handleSubmit, errors } = useSignInController();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -41,7 +61,7 @@ export function SignInForm({
|
|||
variant="outline"
|
||||
className="w-full bg-[#1C1C1C] text-white border-gray-800 hover:bg-[#2C2C2C] hover:border-gray-700"
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
disabled={loading}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
Continue with SSO
|
||||
|
@ -57,28 +77,28 @@ export function SignInForm({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4" {...props} noValidate>
|
||||
<form onSubmit={onSubmit} className="space-y-4" {...props} noValidate>
|
||||
<FormField
|
||||
label="Email"
|
||||
input={
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
{...register("email")}
|
||||
name="email"
|
||||
placeholder="you@example.com"
|
||||
className={`bg-[#1C1C1C] border-gray-800 ${errors.email ? "ring-red-500 focus-visible:ring-red-500" : ""
|
||||
className={`bg-[#1C1C1C] border-gray-800 ${error ? "ring-red-500 focus-visible:ring-red-500" : ""
|
||||
}`}
|
||||
disabled={isPending}
|
||||
disabled={loading}
|
||||
/>
|
||||
}
|
||||
error={errors.email ? errors.email.message : undefined}
|
||||
error={error ? error : undefined}
|
||||
/>
|
||||
<Button
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
size="lg"
|
||||
disabled={isPending}
|
||||
disabled={loading}
|
||||
>
|
||||
{isPending ? (
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
Signing in...
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation"
|
||||
import { getInjection } from "@/di/container"
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import { InputParseError } from "@/src/entities/errors/common"
|
||||
import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth"
|
||||
import { createClient } from "@/app/_utils/supabase/server"
|
||||
|
||||
export async function signIn(formData: FormData) {
|
||||
const instrumentationService = getInjection("IInstrumentationService")
|
||||
return await instrumentationService.instrumentServerAction("signIn", {
|
||||
recordResponse: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const email = formData.get("email")?.toString()
|
||||
|
||||
const signInController = getInjection("ISignInController")
|
||||
await signInController({ email })
|
||||
|
||||
if (email) redirect(`/verify-otp?email=${encodeURIComponent(email)}`)
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof InputParseError ||
|
||||
err instanceof AuthenticationError
|
||||
) {
|
||||
return {
|
||||
error: 'Incorrect credential. Please try again.',
|
||||
};
|
||||
}
|
||||
|
||||
const crashReporterService = getInjection('ICrashReporterService');
|
||||
crashReporterService.report(err);
|
||||
|
||||
return {
|
||||
error:
|
||||
'An error happened. The developers have been notified. Please try again later.',
|
||||
};
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// export async function signUp(formData: FormData) {
|
||||
// const instrumentationService = getInjection("IInstrumentationService")
|
||||
// return await instrumentationService.instrumentServerAction("signUp", { recordResponse: true }, async () => {
|
||||
// try {
|
||||
// const data = Object.fromEntries(formData.entries())
|
||||
// const signUpController = getInjection("ISignUpController")
|
||||
// await signUpController(data)
|
||||
// } catch (err) {
|
||||
// if (err instanceof InputParseError) {
|
||||
// return { error: err.message, success: false }
|
||||
// }
|
||||
|
||||
// const crashReporterService = getInjection("ICrashReporterService")
|
||||
// crashReporterService.report(err)
|
||||
|
||||
// return {
|
||||
// error: "An error occurred during sign up. Please try again later.",
|
||||
// success: false,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
export async function signOut() {
|
||||
const instrumentationService = getInjection("IInstrumentationService")
|
||||
return await instrumentationService.instrumentServerAction("signOut", {
|
||||
recordResponse: true
|
||||
}, async () => {
|
||||
try {
|
||||
const signOutController = getInjection("ISignOutController")
|
||||
await signOutController()
|
||||
|
||||
revalidatePath("/")
|
||||
redirect("/sign-in") // Updated to match your route
|
||||
} catch (err) {
|
||||
const crashReporterService = getInjection("ICrashReporterService")
|
||||
crashReporterService.report(err)
|
||||
|
||||
return {
|
||||
error: "An error occurred during sign out. Please try again later.",
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function verifyOtp(formData: FormData) {
|
||||
const instrumentationService = getInjection("IInstrumentationService")
|
||||
return await instrumentationService.instrumentServerAction("verifyOtp", {
|
||||
recordResponse: true
|
||||
}, async () => {
|
||||
try {
|
||||
const email = formData.get("email")?.toString()
|
||||
const token = formData.get("token")?.toString()
|
||||
|
||||
const verifyOtpController = getInjection("IVerifyOtpController")
|
||||
await verifyOtpController({ email, token })
|
||||
|
||||
redirect("/dashboard") // Updated to match your route
|
||||
} catch (err) {
|
||||
if (err instanceof InputParseError || err instanceof AuthenticationError) {
|
||||
return { error: err.message, success: false }
|
||||
}
|
||||
|
||||
const crashReporterService = getInjection("ICrashReporterService")
|
||||
crashReporterService.report(err)
|
||||
|
||||
return {
|
||||
error: "An error occurred during OTP verification. Please try again later.",
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { redirect } from "next/navigation";
|
||||
import { checkSession } from "./_actions/session";
|
||||
// import { checkSession } from "./_actions/session";
|
||||
import { createClient } from "@/app/_utils/supabase/client";
|
||||
|
||||
export default async function Layout({
|
||||
|
|
|
@ -27,3 +27,5 @@ export const createClient = async () => {
|
|||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { createContainer } from '@evyweb/ioctopus';
|
||||
import { DI_RETURN_TYPES, DI_SYMBOLS } from '@/di/types';
|
||||
import { IInstrumentationService } from '@/src/application/services/instrumentation.service.interface';
|
||||
import { createAuthenticationModule } from './modules/authentication.module';
|
||||
|
||||
const ApplicationContainer = createContainer();
|
||||
ApplicationContainer.load(Symbol('AuthenticationModule'), createAuthenticationModule());
|
||||
|
||||
export function getInjection<K extends keyof typeof DI_SYMBOLS>(
|
||||
symbol: K
|
||||
): DI_RETURN_TYPES[K] {
|
||||
const instrumentationService =
|
||||
ApplicationContainer.get<IInstrumentationService>(
|
||||
DI_SYMBOLS.IInstrumentationService
|
||||
);
|
||||
|
||||
return instrumentationService.startSpan(
|
||||
{
|
||||
name: '(di) getInjection',
|
||||
op: 'function',
|
||||
attributes: { symbol: symbol.toString() },
|
||||
},
|
||||
() => ApplicationContainer.get(DI_SYMBOLS[symbol])
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { createModule } from '@evyweb/ioctopus';
|
||||
|
||||
import { DI_SYMBOLS } from '@/di/types';
|
||||
import { signInController, signOutController, verifyOtpController } from '@/src/interface-adapters/controllers/auth/authentication-controller';
|
||||
import { AuthenticationService } from '@/src/infrastructure/services/authentication.service';
|
||||
import { IInstrumentationServiceImpl } from '@/src/application/services/instrumentation.service.interface';
|
||||
|
||||
export function createAuthenticationModule() {
|
||||
const authenticationModule = createModule();
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IAuthenticationService)
|
||||
.toClass(AuthenticationService, [
|
||||
DI_SYMBOLS.IUsersRepository,
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
]);
|
||||
|
||||
// Rest of your bindings remain the same
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignInController)
|
||||
.toHigherOrderFunction(signInController, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.ISignInUseCase,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.IVerifyOtpController)
|
||||
.toHigherOrderFunction(verifyOtpController, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.IVerifyOtpUseCase,
|
||||
]);
|
||||
|
||||
authenticationModule
|
||||
.bind(DI_SYMBOLS.ISignOutController)
|
||||
.toHigherOrderFunction(signOutController, [
|
||||
DI_SYMBOLS.IInstrumentationService,
|
||||
DI_SYMBOLS.ISignOutUseCase,
|
||||
]);
|
||||
|
||||
return authenticationModule;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { createModule } from '@evyweb/ioctopus';
|
||||
|
||||
import { DI_SYMBOLS } from '@/di/types';
|
||||
|
||||
export function createMonitoringModule() {
|
||||
const monitoringModule = createModule();
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
monitoringModule
|
||||
.bind(DI_SYMBOLS.IInstrumentationService)
|
||||
.toClass(MockInstrumentationService);
|
||||
monitoringModule
|
||||
.bind(DI_SYMBOLS.ICrashReporterService)
|
||||
.toClass(MockCrashReporterService);
|
||||
} else {
|
||||
monitoringModule
|
||||
.bind(DI_SYMBOLS.IInstrumentationService)
|
||||
.toClass(InstrumentationService);
|
||||
monitoringModule
|
||||
.bind(DI_SYMBOLS.ICrashReporterService)
|
||||
.toClass(CrashReporterService);
|
||||
}
|
||||
|
||||
return monitoringModule;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { IAuthenticationService } from '@/src/application/services/authentication.service.interface';
|
||||
import { ITransactionManagerService } from '@/src/application/services/transaction-manager.service.interface';
|
||||
import { IInstrumentationService } from '@/src/application/services/instrumentation.service.interface';
|
||||
import { ICrashReporterService } from '@/src/application/services/crash-reporter.service.interface';
|
||||
|
||||
import { ISignInUseCase } from '@/src/application/use-cases/auth/sign-in.use-case';
|
||||
import { ISignUpUseCase } from '@/src/application/use-cases/auth/sign-up.use-case';
|
||||
import { ISignOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case';
|
||||
import { IUsersRepository } from '@/src/application/repositories/users.repository';
|
||||
import { ISignInController, ISignOutController, IVerifyOtpController } from '@/src/interface-adapters/controllers/auth/authentication-controller';
|
||||
import { IVerifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case';
|
||||
import { IInviteUserUseCase } from '@/src/application/use-cases/users/invite-user.use-case';
|
||||
|
||||
export const DI_SYMBOLS = {
|
||||
// Services
|
||||
IAuthenticationService: Symbol.for('IAuthenticationService'),
|
||||
ITransactionManagerService: Symbol.for('ITransactionManagerService'),
|
||||
IInstrumentationService: Symbol.for('IInstrumentationService'),
|
||||
ICrashReporterService: Symbol.for('ICrashReporterService'),
|
||||
|
||||
// Repositories
|
||||
IUsersRepository: Symbol.for('IUsersRepository'),
|
||||
|
||||
// Use Cases
|
||||
ISignInUseCase: Symbol.for('ISignInUseCase'),
|
||||
ISignOutUseCase: Symbol.for('ISignOutUseCase'),
|
||||
IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'),
|
||||
|
||||
// Controllers
|
||||
ISignInController: Symbol.for('ISignInController'),
|
||||
ISignOutController: Symbol.for('ISignOutController'),
|
||||
IVerifyOtpController: Symbol.for('IVerifyOtpController'),
|
||||
};
|
||||
|
||||
export interface DI_RETURN_TYPES {
|
||||
// Services
|
||||
IAuthenticationService: IAuthenticationService;
|
||||
ITransactionManagerService: ITransactionManagerService;
|
||||
IInstrumentationService: IInstrumentationService;
|
||||
ICrashReporterService: ICrashReporterService;
|
||||
|
||||
// Repositories
|
||||
IUsersRepository: IUsersRepository;
|
||||
|
||||
// Use Cases
|
||||
ISignInUseCase: ISignInUseCase;
|
||||
ISignOutUseCase: ISignOutUseCase;
|
||||
IVerifyOtpUseCase: IVerifyOtpUseCase;
|
||||
|
||||
// Controllers
|
||||
ISignInController: ISignInController;
|
||||
ISignOutController: ISignOutController;
|
||||
IVerifyOtpController: IVerifyOtpController;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@evyweb/ioctopus": "^1.2.0",
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
|
@ -1054,6 +1055,12 @@
|
|||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@evyweb/ioctopus": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@evyweb/ioctopus/-/ioctopus-1.2.0.tgz",
|
||||
"integrity": "sha512-OIISYUx7WZDm6uxQkVsKmNF13tEiA3gbUeboTkr4LUTmJffhSVswiWAs8Ng5DoyvUlmgteTYcHP5XzOtrPTxLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"seed": "ts-node prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@evyweb/ioctopus": "^1.2.0",
|
||||
"@hookform/resolvers": "^4.1.2",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient();
|
||||
return new PrismaClient({
|
||||
log: [
|
||||
"query",
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
declare const globalThis: {
|
||||
|
@ -16,3 +20,4 @@ if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = db;
|
|||
|
||||
|
||||
export type Transaction = PrismaClient['$transaction'];
|
||||
|
||||
|
|
|
@ -1,179 +1,6 @@
|
|||
// // src/repositories/auth.repository.ts
|
||||
// "use server";
|
||||
|
||||
// import { createClient } from "@/app/_utils/supabase/server";
|
||||
// import { SignInFormData } from "@/src/entities/models/auth/sign-in.model";
|
||||
// import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model";
|
||||
// import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
// import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
|
||||
// import { ICrashReporterService } from "@/src/application/services/crash-reporter.service.interface";
|
||||
// import { createAdminClient } from "@/app/_utils/supabase/admin";
|
||||
// import { DatabaseOperationError } from "@/src/entities/errors/common";
|
||||
|
||||
// export class AuthRepository {
|
||||
// private static instance: AuthRepository;
|
||||
|
||||
// private constructor(
|
||||
// private readonly instrumentationService: IInstrumentationService,
|
||||
// private readonly crashReporterService: ICrashReporterService,
|
||||
// private readonly supabaseAdmin = createAdminClient(),
|
||||
// private readonly supabaseServer = createClient()
|
||||
// ) { }
|
||||
|
||||
// // Method untuk mendapatkan singleton instance
|
||||
// public static getInstance(
|
||||
// instrumentationService: IInstrumentationService,
|
||||
// crashReporterService: ICrashReporterService
|
||||
// ): AuthRepository {
|
||||
// if (!AuthRepository.instance) {
|
||||
// AuthRepository.instance = new AuthRepository(instrumentationService, crashReporterService);
|
||||
// }
|
||||
// return AuthRepository.instance;
|
||||
// }
|
||||
|
||||
// async signIn({ email }: SignInFormData) {
|
||||
// return await this.instrumentationService.startSpan({
|
||||
// name: "UsersRepository > signIn",
|
||||
// op: 'db.query',
|
||||
// attributes: { 'db.system': 'postgres' },
|
||||
// }, async () => {
|
||||
// try {
|
||||
// const supabase = await this.supabaseServer;
|
||||
// const { data, error } = await supabase.auth.signInWithOtp({
|
||||
// email,
|
||||
// options: {
|
||||
// shouldCreateUser: false,
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (error) {
|
||||
// console.error("Error signing in:", error);
|
||||
// throw new AuthenticationError(error.message);
|
||||
// }
|
||||
|
||||
// return {
|
||||
// data,
|
||||
// redirectTo: `/verify-otp?email=${encodeURIComponent(email)}`,
|
||||
// };
|
||||
// } catch (err) {
|
||||
// this.crashReporterService.report(err);
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// async verifyOtp({ email, token }: VerifyOtpFormData) {
|
||||
// return await this.instrumentationService.startSpan({
|
||||
// name: "UsersRepository > verifyOtp",
|
||||
// op: 'db.query',
|
||||
// attributes: { 'db.system': 'postgres' },
|
||||
// }, async () => {
|
||||
// try {
|
||||
// const supabase = await this.supabaseServer;
|
||||
// const { data, error } = await supabase.auth.verifyOtp({
|
||||
// email,
|
||||
// token,
|
||||
// type: "email",
|
||||
// });
|
||||
|
||||
// if (error) {
|
||||
// console.error("Error verifying OTP:", error);
|
||||
// throw new AuthenticationError(error.message);
|
||||
// }
|
||||
|
||||
// return {
|
||||
// data,
|
||||
// redirectTo: "/dashboard",
|
||||
// };
|
||||
// } catch (err) {
|
||||
// this.crashReporterService.report(err);
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// async signOut() {
|
||||
// return await this.instrumentationService.startSpan({
|
||||
// name: "UsersRepository > signOut",
|
||||
// op: 'db.query',
|
||||
// attributes: { 'db.system': 'postgres' },
|
||||
// }, async () => {
|
||||
// try {
|
||||
// const supabase = await this.supabaseServer;
|
||||
// const { error } = await supabase.auth.signOut();
|
||||
|
||||
// if (error) {
|
||||
// console.error("Error signing out:", error);
|
||||
// throw new AuthenticationError(error.message);
|
||||
// }
|
||||
|
||||
// return {
|
||||
// success: true,
|
||||
// redirectTo: "/",
|
||||
// };
|
||||
// } catch (err) {
|
||||
// this.crashReporterService.report(err);
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// async sendPasswordRecovery(email: string): Promise<void> {
|
||||
// return await this.instrumentationService.startSpan({
|
||||
// name: "UsersRepository > sendPasswordRecovery",
|
||||
// op: 'db.query',
|
||||
// attributes: { 'db.system': 'postgres' },
|
||||
// }, async () => {
|
||||
// try {
|
||||
// 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);
|
||||
// }
|
||||
// } catch (err) {
|
||||
// this.crashReporterService.report(err);
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// async sendMagicLink(email: string): Promise<void> {
|
||||
// return await this.instrumentationService.startSpan({
|
||||
// name: "UsersRepository > sendMagicLink",
|
||||
// op: 'db.query',
|
||||
// attributes: { 'db.system': 'postgres' },
|
||||
// }, async () => {
|
||||
// try {
|
||||
// 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);
|
||||
// }
|
||||
// } catch (err) {
|
||||
// this.crashReporterService.report(err);
|
||||
// throw err;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// src/app/_actions/auth.actions.ts
|
||||
"use server";
|
||||
|
||||
import { createClient } from "@/app/_utils/supabase/server";
|
||||
import { createClient as createServerClient } from "@/app/_utils/supabase/server";
|
||||
import { SignInFormData } from "@/src/entities/models/auth/sign-in.model";
|
||||
import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model";
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
|
@ -182,6 +9,9 @@ import { createAdminClient } from "@/app/_utils/supabase/admin";
|
|||
import { IInstrumentationServiceImpl } from "@/src/application/services/instrumentation.service.interface";
|
||||
import { ICrashReporterServiceImpl } from "@/src/application/services/crash-reporter.service.interface";
|
||||
|
||||
let supabaseAdmin = createAdminClient();
|
||||
let supabaseServer = createServerClient();
|
||||
|
||||
// Server actions for authentication
|
||||
export async function signIn({ email }: SignInFormData) {
|
||||
return await IInstrumentationServiceImpl.instrumentServerAction(
|
||||
|
@ -189,7 +19,7 @@ export async function signIn({ email }: SignInFormData) {
|
|||
{ email },
|
||||
async () => {
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const supabase = await supabaseServer;
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email,
|
||||
options: {
|
||||
|
@ -225,7 +55,7 @@ export async function verifyOtp({ email, token }: VerifyOtpFormData) {
|
|||
{ email },
|
||||
async () => {
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const supabase = await supabaseServer;
|
||||
const { data, error } = await supabase.auth.verifyOtp({
|
||||
email,
|
||||
token,
|
||||
|
@ -260,7 +90,7 @@ export async function signOut() {
|
|||
{},
|
||||
async () => {
|
||||
try {
|
||||
const supabase = await createClient();
|
||||
const supabase = await supabaseServer;
|
||||
const { error } = await supabase.auth.signOut();
|
||||
|
||||
if (error) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
import { AuthResult } from "@/src/entities/models/auth/auth-result.model"
|
||||
import { Session } from "@/src/entities/models/auth/session.model"
|
||||
import { SignInFormData, SignInPasswordless, SignInWithPassword } from "@/src/entities/models/auth/sign-in.model"
|
||||
import { SignUpWithEmail, SignUpWithPhone } from "@/src/entities/models/auth/sign-up.model"
|
||||
import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model"
|
||||
import { User } from "@/src/entities/models/users/users.model"
|
||||
|
||||
export interface IAuthenticationService {
|
||||
signInPasswordless(credentials: SignInPasswordless): Promise<void>
|
||||
signInWithPassword(credentials: SignInWithPassword): Promise<void>
|
||||
signUpWithEmail(credentials: SignUpWithEmail): Promise<User>
|
||||
signUpWithPhone(credentials: SignUpWithPhone): Promise<User>
|
||||
getSession(): Promise<Session | null>
|
||||
signOut(): Promise<void>
|
||||
sendMagicLink(email: string): Promise<void>
|
||||
sendPasswordRecovery(email: string): Promise<void>
|
||||
verifyOtp(credentials: VerifyOtpFormData): Promise<void>
|
||||
}
|
|
@ -1,22 +1,27 @@
|
|||
export interface IInstrumentationService {
|
||||
startSpan<T>(
|
||||
options: { name: string; op?: string; attributes?: Record<string, any> },
|
||||
callback: () => T
|
||||
): T;
|
||||
instrumentServerAction<T>(
|
||||
name: string,
|
||||
options: Record<string, any>,
|
||||
callback: () => T
|
||||
): Promise<T>;
|
||||
}
|
||||
|
||||
class InstrumentationService implements IInstrumentationService {
|
||||
startSpan<T>(
|
||||
options: { name: string; op?: string; attributes?: Record<string, any> },
|
||||
callback: () => T
|
||||
): T;
|
||||
instrumentServerAction<T>(
|
||||
name: string,
|
||||
options: Record<string, any>,
|
||||
callback: () => T
|
||||
): Promise<T>;
|
||||
}
|
||||
|
||||
class InstrumentationService implements IInstrumentationService {
|
||||
startSpan<T>(
|
||||
spanAttributes: { name: string; op: string; attributes: Record<string, string> },
|
||||
callback: () => T
|
||||
): T {
|
||||
// Implementation of the startSpan method
|
||||
return callback();
|
||||
// Your implementation here
|
||||
console.log(`Starting span: ${spanAttributes.name}`);
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
console.log(`Ending span: ${spanAttributes.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
async instrumentServerAction<T>(
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
export type ISendMagicLinkUseCase = ReturnType<typeof sendMagicLinkUseCase>
|
||||
|
||||
export const sendMagicLinkUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (input: { email: string }): Promise<void> => {
|
||||
return await instrumentationService.startSpan({ name: "sendMagicLink Use Case", op: "function" },
|
||||
async () => {
|
||||
const user = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
await authenticationService.sendMagicLink(input.email)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
export type ISendPasswordRecoveryUseCase = ReturnType<typeof sendPasswordRecoveryUseCase>
|
||||
|
||||
export const sendPasswordRecoveryUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (input: { email: string }): Promise<void> => {
|
||||
return await instrumentationService.startSpan({ name: "sendPasswordRecovery Use Case", op: "function" },
|
||||
async () => {
|
||||
const user = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
await authenticationService.sendPasswordRecovery(input.email)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import type { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
import { InputParseError, NotFoundError } from "@/src/entities/errors/common";
|
||||
import { type SignInFormData, SignInPasswordless, SignInSchema } from "@/src/entities/models/auth/sign-in.model"
|
||||
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface";
|
||||
import { User } from "@/src/entities/models/users/users.model";
|
||||
import { Session } from "@/src/entities/models/auth/session.model";
|
||||
import { IUsersRepository } from "../../repositories/users.repository";
|
||||
import { AuthResult } from "@/src/entities/models/auth/auth-result.model";
|
||||
|
||||
export type ISignInUseCase = ReturnType<typeof signInUseCase>
|
||||
|
||||
export const signInUseCase =
|
||||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
usersRepository: IUsersRepository
|
||||
) =>
|
||||
async (input: SignInPasswordless): Promise<void> => {
|
||||
return instrumentationService.startSpan({ name: "signIn Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User does not exist")
|
||||
}
|
||||
|
||||
// Attempt to sign in
|
||||
await authenticationService.signInPasswordless({
|
||||
email: input.email
|
||||
})
|
||||
|
||||
const session = await authenticationService.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new NotFoundError("Session not found")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
|
||||
export type ISignOutUseCase = ReturnType<typeof signOutUseCase>
|
||||
|
||||
export const signOutUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService
|
||||
) => async (): Promise<void> => {
|
||||
return await instrumentationService.startSpan({ name: "signOut Use Case", op: "function" },
|
||||
async () => {
|
||||
await authenticationService.signOut()
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { CreateUser, User } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { SignUpFormData } from "@/src/entities/models/auth/sign-up.model"
|
||||
import { AuthResult } from "@/src/entities/models/auth/auth-result.model"
|
||||
|
||||
|
||||
export type ISignUpUseCase = ReturnType<typeof signUpUseCase>
|
||||
|
||||
export const signUpUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (input: SignUpFormData): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "signUp Use Case", op: "function" },
|
||||
async () => {
|
||||
const existingUser = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (existingUser) {
|
||||
throw new AuthenticationError("User already exists")
|
||||
}
|
||||
|
||||
const newUser = await authenticationService.signUpWithEmail({
|
||||
email: input.email,
|
||||
password: input.password
|
||||
})
|
||||
|
||||
await authenticationService.signInWithPassword({
|
||||
email: input.email,
|
||||
password: input.password
|
||||
})
|
||||
|
||||
const session = await authenticationService.getSession();
|
||||
|
||||
if (!session) {
|
||||
throw new AuthenticationError("Session not found")
|
||||
}
|
||||
|
||||
return {
|
||||
...newUser
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
|
||||
export type IVerifyOtpUseCase = ReturnType<typeof verifyOtpUseCase>
|
||||
|
||||
export const verifyOtpUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (input: VerifyOtpFormData): Promise<void> => {
|
||||
return await instrumentationService.startSpan({ name: "verifyOtp Use Case", op: "function" },
|
||||
async () => {
|
||||
const user = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
await authenticationService.verifyOtp({
|
||||
email: input.email,
|
||||
token: input.token
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { User } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
export type IBanUserUseCase = ReturnType<typeof banUserUseCase>
|
||||
|
||||
export const banUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (id: string, ban_duration: string): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "banUser Use Case", op: "function" },
|
||||
async () => {
|
||||
const existingUser = await usersRepository.getUserById(id)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
const bannedUser = await usersRepository.banUser(id, ban_duration)
|
||||
|
||||
return {
|
||||
...bannedUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { CreateUser, User } from "@/src/entities/models/users/users.model"
|
||||
import { InputParseError } from "@/src/entities/errors/common"
|
||||
|
||||
|
||||
export type ICreateUserUseCase = ReturnType<typeof createUserUseCase>
|
||||
|
||||
export const createUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository,
|
||||
) => async (input: CreateUser): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "createUser Use Case", op: "function" },
|
||||
async () => {
|
||||
const existingUser = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (existingUser) {
|
||||
throw new AuthenticationError("User already exists")
|
||||
}
|
||||
|
||||
const newUser = await usersRepository.createUser({
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
email_confirm: true
|
||||
})
|
||||
|
||||
if (!newUser) {
|
||||
throw new InputParseError("User not created")
|
||||
}
|
||||
|
||||
return {
|
||||
...newUser
|
||||
};
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
|
||||
export type IDeleteUserUseCase = ReturnType<typeof deleteUserUseCase>
|
||||
|
||||
const deleteUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (id: string): Promise<void> => {
|
||||
return await instrumentationService.startSpan({ name: "deleteUser Use Case", op: "function" },
|
||||
async () => {
|
||||
await usersRepository.deleteUser(id)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { UserResponse } from "@/src/entities/models/users/users.model"
|
||||
|
||||
|
||||
export type IGetCurrentUserUseCase = ReturnType<typeof getCurrentUserUseCase>
|
||||
|
||||
export const getCurrentUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (): Promise<UserResponse> => {
|
||||
return await instrumentationService.startSpan({ name: "getCurrentUser Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getCurrentUser()
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
return {
|
||||
...existingUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { User } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
|
||||
export type IGetListUsersUseCase = ReturnType<typeof getListUsersUseCase>
|
||||
|
||||
export const getListUsersUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (): Promise<User[]> => {
|
||||
return await instrumentationService.startSpan({ name: "getListUsers Use Case", op: "function" },
|
||||
async () => {
|
||||
return await usersRepository.listUsers()
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { User } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
export type IGetUserByEmailUseCase = ReturnType<typeof getUserByEmailUseCase>
|
||||
|
||||
const getUserByEmailUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (email: string): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "getUserByEmail Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getUserByEmail(email)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
return {
|
||||
...existingUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { User } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
|
||||
export type IGetUserByIdUseCase = ReturnType<typeof getUserByIdUseCase>
|
||||
|
||||
export const getUserByIdUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (id: string): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "getUserById Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getUserById(id)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
return {
|
||||
...existingUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { User } from "@/src/entities/models/users/users.model"
|
||||
|
||||
export type IGetUserByUsernameUseCase = ReturnType<typeof getUserByUsernameUseCase>
|
||||
|
||||
const getUserByUsernameUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (username: string): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "getUserByUsername Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getUserByUsername(username)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
return {
|
||||
...existingUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { AuthenticationError } from "@/src/entities/errors/auth"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IAuthenticationService } from "../../services/authentication.service.interface"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { User } from "@/src/entities/models/users/users.model"
|
||||
|
||||
|
||||
export type IInviteUserUseCase = ReturnType<typeof inviteUserUseCase>
|
||||
|
||||
export const inviteUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository,
|
||||
authenticationService: IAuthenticationService,
|
||||
) => async (input: { email: string }): Promise<User> => {
|
||||
return await instrumentationService.startSpan({ name: "inviteUser Use Case", op: "function" },
|
||||
async () => {
|
||||
const existingUser = await usersRepository.getUserByEmail(input.email)
|
||||
|
||||
if (existingUser) {
|
||||
throw new AuthenticationError("User already exists")
|
||||
}
|
||||
|
||||
const newUser = await usersRepository.inviteUser(input.email)
|
||||
|
||||
if (!newUser) {
|
||||
throw new AuthenticationError("User not invited")
|
||||
}
|
||||
|
||||
return {
|
||||
...newUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { UpdateUser, User, UserResponse } from "@/src/entities/models/users/users.model"
|
||||
import { IUsersRepository } from "../../repositories/users.repository"
|
||||
import { IInstrumentationService } from "../../services/instrumentation.service.interface"
|
||||
import { NotFoundError } from "@/src/entities/errors/common"
|
||||
|
||||
export type IUpdateUserUseCase = ReturnType<typeof updateUserUseCase>
|
||||
|
||||
const updateUserUseCase = (
|
||||
instrumentationService: IInstrumentationService,
|
||||
usersRepository: IUsersRepository
|
||||
) => async (id: string, input: UpdateUser): Promise<UserResponse> => {
|
||||
return await instrumentationService.startSpan({ name: "updateUser Use Case", op: "function" },
|
||||
async () => {
|
||||
|
||||
const existingUser = await usersRepository.getUserById(id)
|
||||
|
||||
if (!existingUser) {
|
||||
throw new NotFoundError("User not found")
|
||||
}
|
||||
|
||||
const updatedUser = await usersRepository.updateUser(id, input)
|
||||
|
||||
return {
|
||||
...updatedUser
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
import { Session } from '@/src/entities/models/auth/session.model';
|
||||
|
||||
export interface AuthResult {
|
||||
data: {
|
||||
user: null;
|
||||
session: Session | null;
|
||||
messageId?: string | null;
|
||||
} | undefined;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { z } from "zod";
|
||||
import { UserSchema } from "@/src/entities/models/users/users.model";
|
||||
|
||||
export const SessionSchema = z.object({
|
||||
user: UserSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
role: true,
|
||||
}),
|
||||
expiresAt: z.number().optional(),
|
||||
});
|
||||
|
||||
export type Session = z.infer<typeof SessionSchema>
|
|
@ -6,12 +6,43 @@ export const SignInSchema = z.object({
|
|||
.string()
|
||||
.min(1, { message: "Email is required" })
|
||||
.email({ message: "Please enter a valid email address" }),
|
||||
password: z.string().min(1, { message: "Password is required" }),
|
||||
phone: z.string().optional(),
|
||||
});
|
||||
|
||||
// Export the type derived from the schema
|
||||
export type SignInFormData = z.infer<typeof SignInSchema>;
|
||||
|
||||
export const SignInWithPassword = SignInSchema.pick({
|
||||
email: true,
|
||||
password: true,
|
||||
phone: true
|
||||
})
|
||||
|
||||
// Default values for the form
|
||||
export const defaultSignInValues: SignInFormData = {
|
||||
export const defaultSignInWithPasswordValues: SignInWithPassword = {
|
||||
email: "",
|
||||
password: "",
|
||||
phone: ""
|
||||
};
|
||||
|
||||
export type SignInWithPassword = z.infer<typeof SignInWithPassword>
|
||||
|
||||
export const SignInPasswordless = SignInSchema.pick({
|
||||
email: true,
|
||||
})
|
||||
|
||||
// Default values for the form
|
||||
export const defaultSignInPasswordlessValues: SignInPasswordless = {
|
||||
email: "",
|
||||
}
|
||||
|
||||
export type SignInPasswordless = z.infer<typeof SignInPasswordless>
|
||||
|
||||
|
||||
// Define the sign-in response schema using Zod
|
||||
export const SignInResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
redirectTo: z.string().optional(),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const SignUpSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: "Email is required" })
|
||||
.email({ message: "Please enter a valid email address" }),
|
||||
password: z.string().min(1, { message: "Password is required" }),
|
||||
phone: z.string().optional(),
|
||||
})
|
||||
|
||||
export type SignUpFormData = z.infer<typeof SignUpSchema>;
|
||||
|
||||
export const SignUpWithEmail = SignUpSchema.pick({
|
||||
email: true,
|
||||
password: true,
|
||||
})
|
||||
|
||||
export const defaultSignUpWithEmailValues: SignUpWithEmail = {
|
||||
email: "",
|
||||
password: "",
|
||||
}
|
||||
|
||||
export type SignUpWithEmail = z.infer<typeof SignUpWithEmail>
|
||||
|
||||
export const SignUpWithPhone = SignUpSchema.pick({
|
||||
phone: true,
|
||||
password: true,
|
||||
})
|
||||
|
||||
export const defaultSignUpWithPhoneValues: SignUpWithPhone = {
|
||||
phone: "",
|
||||
password: "",
|
||||
}
|
||||
|
||||
export type SignUpWithPhone = z.infer<typeof SignUpWithPhone>
|
|
@ -11,3 +11,4 @@ export const defaultVerifyOtpValues: VerifyOtpFormData = {
|
|||
email: "",
|
||||
token: "",
|
||||
};
|
||||
|
||||
|
|
|
@ -82,17 +82,16 @@ export const ProfileSchema = z.object({
|
|||
|
||||
export type Profile = z.infer<typeof ProfileSchema>;
|
||||
|
||||
export const CreateUserParamsSchema = z.object({
|
||||
export const CreateUserSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
password: z.string().min(8),
|
||||
phone: z.string().optional(),
|
||||
user_metadata: z.record(z.any()).optional(),
|
||||
email_confirm: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type CreateUserParams = z.infer<typeof CreateUserParamsSchema>;
|
||||
export type CreateUser = z.infer<typeof CreateUserSchema>;
|
||||
|
||||
export const UpdateUserParamsSchema = z.object({
|
||||
export const UpdateUserSchema = z.object({
|
||||
email: z.string().email().optional(),
|
||||
email_confirmed_at: z.boolean().optional(),
|
||||
encrypted_password: z.string().optional(),
|
||||
|
@ -120,17 +119,15 @@ export const UpdateUserParamsSchema = z.object({
|
|||
address: z.any().optional(),
|
||||
birth_date: z.date().optional(),
|
||||
})
|
||||
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type UpdateUserParams = z.infer<typeof UpdateUserParamsSchema>;
|
||||
export type UpdateUser = z.infer<typeof UpdateUserSchema>;
|
||||
|
||||
export const InviteUserParamsSchema = z.object({
|
||||
export const InviteUserSchema = z.object({
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export type InviteUserParams = z.infer<typeof InviteUserParamsSchema>;
|
||||
export type InviteUser = z.infer<typeof InviteUserSchema>;
|
||||
|
||||
export type UserResponse =
|
||||
| {
|
||||
|
|
|
@ -0,0 +1,452 @@
|
|||
import { IUsersRepository } from "@/src/application/repositories/users.repository";
|
||||
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 { 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 listUsers(): 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;
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
return user;
|
||||
} 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)
|
||||
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)
|
||||
return user;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<UserResponse> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "UsersRepository > getCurrentUser",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer;
|
||||
|
||||
const query = supabase.auth.getUser();
|
||||
|
||||
const user = await this.instrumentationService.startSpan({
|
||||
name: "UsersRepository > getCurrentUser > supabase.auth.getUser",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" },
|
||||
},
|
||||
async () => {
|
||||
return await query;
|
||||
}
|
||||
)
|
||||
|
||||
return user;
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async createUser(input: CreateUser, tx?: ITransaction): Promise<User | null> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "UsersRepository > createUser",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = this.supabaseAdmin;
|
||||
|
||||
const query = supabase.auth.admin.createUser({
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
email_confirm: input.email_confirm,
|
||||
})
|
||||
|
||||
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;
|
||||
}
|
||||
)
|
||||
|
||||
return user;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async inviteUser(email: string, tx?: ITransaction): Promise<User | null> {
|
||||
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;
|
||||
}
|
||||
)
|
||||
|
||||
return user;
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async updateUser(id: string, input: Partial<UpdateUser>, tx?: ITransaction): Promise<UserResponse> {
|
||||
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 AuthenticationError(error.message);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
data: {
|
||||
user: {
|
||||
...data.user,
|
||||
role: updatedUser.role,
|
||||
profile: {
|
||||
user_id: id,
|
||||
...updatedUser.profile,
|
||||
},
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async deleteUser(id: string, tx?: ITransaction): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "UsersRepository > deleteUser",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = this.supabaseAdmin;
|
||||
|
||||
const query = supabase.auth.admin.deleteUser(id);
|
||||
|
||||
await this.instrumentationService.startSpan({
|
||||
name: "UsersRepository > deleteUser > supabase.auth.admin.deleteUser",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" },
|
||||
},
|
||||
async () => {
|
||||
return await query;
|
||||
}
|
||||
)
|
||||
|
||||
return;
|
||||
|
||||
} 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, 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 AuthenticationError(error.message);
|
||||
}
|
||||
|
||||
return data.user;
|
||||
|
||||
} 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, 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 AuthenticationError(error.message);
|
||||
}
|
||||
|
||||
return data.user;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
import { createAdminClient } from "@/app/_utils/supabase/admin";
|
||||
import { createClient } from "@/app/_utils/supabase/server";
|
||||
import db from "@/prisma/db";
|
||||
import { IUsersRepository } from "@/src/application/repositories/users.repository";
|
||||
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface";
|
||||
import { ICrashReporterService } from "@/src/application/services/crash-reporter.service.interface";
|
||||
import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
|
||||
import { AuthenticationError } from "@/src/entities/errors/auth";
|
||||
import { Session } from "@/src/entities/models/auth/session.model";
|
||||
import { SignInPasswordless, SignInWithPassword } from "@/src/entities/models/auth/sign-in.model";
|
||||
import { SignUpWithEmail, SignUpWithPhone } from "@/src/entities/models/auth/sign-up.model";
|
||||
import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model";
|
||||
import { User } from "@/src/entities/models/users/users.model";
|
||||
|
||||
export class AuthenticationService implements IAuthenticationService {
|
||||
constructor(
|
||||
private readonly usersRepository: IUsersRepository,
|
||||
private readonly instrumentationService: IInstrumentationService,
|
||||
private readonly crashReporterService: ICrashReporterService,
|
||||
private readonly supabaseAdmin = createAdminClient(),
|
||||
private readonly supabaseServer = createClient()
|
||||
) { }
|
||||
|
||||
async signInPasswordless(credentials: SignInPasswordless): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "signInPasswordless Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const { email } = credentials
|
||||
const signIn = supabase.auth.signInWithOtp({ email })
|
||||
|
||||
const { data: { session }, error } = await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signInWithOtp",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await signIn
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async signInWithPassword(credentials: SignInWithPassword): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "signInWithPassword Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const { email, password } = credentials
|
||||
const signIn = supabase.auth.signInWithPassword({ email, password })
|
||||
|
||||
const { data: { session }, error } = await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signIn",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await signIn
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async signUpWithEmail(credentials: SignUpWithEmail): Promise<User> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "signUpWithEmail Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const { email, password } = credentials
|
||||
const signUp = supabase.auth.signUp({ email, password })
|
||||
|
||||
const { data: { user, session }, error } = await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signUp",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await signUp
|
||||
})
|
||||
|
||||
const newUser = db.users.findUnique({
|
||||
where: {
|
||||
id: user!.id
|
||||
},
|
||||
include: {
|
||||
profile: true
|
||||
}
|
||||
})
|
||||
|
||||
const userDetail = await this.instrumentationService.startSpan({
|
||||
name: "db.users.findUnique",
|
||||
op: "db:query",
|
||||
attributes: { "system": "prisma" }
|
||||
}, async () => {
|
||||
return await newUser
|
||||
})
|
||||
|
||||
return userDetail!;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async signUpWithPhone(credentials: SignUpWithPhone): Promise<User> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async getSession(): Promise<Session | null> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "getSession Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const session = supabase.auth.getSession()
|
||||
|
||||
const { data, error } = await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.session",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await session
|
||||
})
|
||||
|
||||
if (!data.session) {
|
||||
throw new AuthenticationError("Session not found")
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: data.session.user.id,
|
||||
role: data.session.user.role,
|
||||
email: data.session.user.email
|
||||
},
|
||||
expiresAt: data.session.expires_at,
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "signOut Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const signOut = supabase.auth.signOut()
|
||||
|
||||
await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signOut",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await signOut
|
||||
})
|
||||
|
||||
return;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async sendMagicLink(email: string): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "sendMagicLink Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const magicLink = supabase.auth.signInWithOtp({ email })
|
||||
|
||||
await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.signIn",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await magicLink
|
||||
})
|
||||
|
||||
return;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async sendPasswordRecovery(email: string): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "sendPasswordRecovery Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const passwordRecovery = supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
|
||||
})
|
||||
|
||||
await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.resetPasswordForEmail",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await passwordRecovery
|
||||
})
|
||||
|
||||
return;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async verifyOtp(credentials: VerifyOtpFormData): Promise<void> {
|
||||
return await this.instrumentationService.startSpan({
|
||||
name: "verifyOtp Use Case",
|
||||
}, async () => {
|
||||
try {
|
||||
const supabase = await this.supabaseServer
|
||||
|
||||
const { email, token } = credentials
|
||||
const verifyOtp = supabase.auth.verifyOtp({ email, token, type: "email" })
|
||||
|
||||
await this.instrumentationService.startSpan({
|
||||
name: "supabase.auth.verifyOtp",
|
||||
op: "db:query",
|
||||
attributes: { "system": "supabase.auth" }
|
||||
}, async () => {
|
||||
return await verifyOtp
|
||||
})
|
||||
|
||||
return;
|
||||
|
||||
} catch (err) {
|
||||
this.crashReporterService.report(err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { z } from "zod"
|
||||
import type { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"
|
||||
import { InputParseError } from "@/src/entities/errors/common"
|
||||
import { ISignInUseCase } from "@/src/application/use-cases/auth/sign-in.use-case"
|
||||
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface"
|
||||
import { ISignUpUseCase } from "@/src/application/use-cases/auth/sign-up.use-case"
|
||||
import { IVerifyOtpUseCase } from "@/src/application/use-cases/auth/verify-otp.use-case"
|
||||
|
||||
// Sign In Controller
|
||||
const signInInputSchema = z.object({
|
||||
email: z.string().email("Please enter a valid email address"),
|
||||
})
|
||||
|
||||
export type ISignInController = ReturnType<typeof signInController>
|
||||
|
||||
export const signInController =
|
||||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
signInUseCase: ISignInUseCase
|
||||
) =>
|
||||
async (input: Partial<z.infer<typeof signInInputSchema>>) => {
|
||||
return await instrumentationService.startSpan({ name: "signIn Controller" }, async () => {
|
||||
const { data, error: inputParseError } = signInInputSchema.safeParse(input)
|
||||
|
||||
if (inputParseError) {
|
||||
throw new InputParseError("Invalid data", { cause: inputParseError })
|
||||
}
|
||||
|
||||
return await signInUseCase({
|
||||
email: data.email
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Verify OTP Controller
|
||||
const verifyOtpInputSchema = z.object({
|
||||
email: z.string().email("Please enter a valid email address"),
|
||||
token: z.string().min(6, "Please enter a valid OTP")
|
||||
})
|
||||
|
||||
export type IVerifyOtpController = ReturnType<typeof verifyOtpController>
|
||||
|
||||
export const verifyOtpController =
|
||||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
verifyOtpUseCase: IVerifyOtpUseCase
|
||||
) =>
|
||||
async (input: Partial<z.infer<typeof verifyOtpInputSchema>>) => {
|
||||
return await instrumentationService.startSpan({ name: "verifyOtp Controller" }, async () => {
|
||||
const { data, error: inputParseError } = verifyOtpInputSchema.safeParse(input)
|
||||
|
||||
if (inputParseError) {
|
||||
throw new InputParseError("Invalid data", { cause: inputParseError })
|
||||
}
|
||||
|
||||
return await verifyOtpUseCase({
|
||||
email: data.email,
|
||||
token: data.token
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Sign Out Controller
|
||||
export type ISignOutController = ReturnType<typeof signOutController>
|
||||
|
||||
export const signOutController =
|
||||
(
|
||||
instrumentationService: IInstrumentationService,
|
||||
authenticationService: IAuthenticationService
|
||||
) =>
|
||||
async () => {
|
||||
return await instrumentationService.startSpan({
|
||||
name: "signOut Controller"
|
||||
}, async () => {
|
||||
return await authenticationService.signOut()
|
||||
})
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
defaultSignInValues,
|
||||
defaultSignInPasswordlessValues,
|
||||
SignInFormData,
|
||||
SignInSchema,
|
||||
} from "@/src/entities/models/auth/sign-in.model";
|
||||
|
@ -173,7 +173,7 @@ export function useSignInController() {
|
|||
formState: { errors },
|
||||
} = useForm<SignInFormData>({
|
||||
resolver: zodResolver(SignInSchema),
|
||||
defaultValues: defaultSignInValues,
|
||||
defaultValues: defaultSignInPasswordlessValues,
|
||||
});
|
||||
|
||||
// Handler untuk submit form
|
||||
|
|
Loading…
Reference in New Issue