From 08ee1867377aa4ac0c1cb8ff08de1597514b97b1 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Thu, 3 Apr 2025 22:54:34 +0700 Subject: [PATCH] add sign in with password --- .../_components/sign-in-with-credential.tsx | 0 .../(auth)/_components/signin-form.tsx | 4 +- .../(pages)/(auth)/_handlers/use-sign-in.ts | 4 +- .../app/(pages)/(auth)/_queries/mutations.ts | 13 +- sigap-website/app/(pages)/(auth)/action.ts | 46 ++++- sigap-website/app/(pages)/(auth)/handler.tsx | 161 ++++++++++++++++++ .../sign-in/_components/carousal-quote.tsx | 50 ++++++ .../app/(pages)/(auth)/sign-in/page.tsx | 43 +---- .../di/modules/authentication.module.ts | 29 +++- sigap-website/di/types.ts | 16 +- .../authentication.service.interface.ts | 6 +- ...se.ts => sign-in-passwordless.use-case.ts} | 2 +- .../auth/sign-in-with-password.use-case.ts | 27 +++ .../src/entities/models/auth/sign-in.model.ts | 6 +- .../services/authentication.service.ts | 6 +- ....ts => sign-in-passwordless.controller.ts} | 8 +- .../auth/sign-in-with-password.controller.ts | 34 ++++ 17 files changed, 379 insertions(+), 76 deletions(-) create mode 100644 sigap-website/app/(pages)/(auth)/_components/sign-in-with-credential.tsx create mode 100644 sigap-website/app/(pages)/(auth)/handler.tsx create mode 100644 sigap-website/app/(pages)/(auth)/sign-in/_components/carousal-quote.tsx rename sigap-website/src/application/use-cases/auth/{sign-in.use-case.ts => sign-in-passwordless.use-case.ts} (95%) create mode 100644 sigap-website/src/application/use-cases/auth/sign-in-with-password.use-case.ts rename sigap-website/src/interface-adapters/controllers/auth/{sign-in.controller.ts => sign-in-passwordless.controller.ts} (74%) create mode 100644 sigap-website/src/interface-adapters/controllers/auth/sign-in-with-password.controller.ts diff --git a/sigap-website/app/(pages)/(auth)/_components/sign-in-with-credential.tsx b/sigap-website/app/(pages)/(auth)/_components/sign-in-with-credential.tsx new file mode 100644 index 0000000..e69de29 diff --git a/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx b/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx index 699755b..95b66d5 100644 --- a/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx +++ b/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx @@ -9,7 +9,7 @@ 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"; + import { useSignInHandler } from "../_handlers/use-sign-in"; export function SignInForm({ @@ -98,7 +98,7 @@ export function SignInForm({ {isPending ? ( <> - Signing in... + Sign in ) : ( "Sign in" diff --git a/sigap-website/app/(pages)/(auth)/_handlers/use-sign-in.ts b/sigap-website/app/(pages)/(auth)/_handlers/use-sign-in.ts index b35b081..e55e021 100644 --- a/sigap-website/app/(pages)/(auth)/_handlers/use-sign-in.ts +++ b/sigap-website/app/(pages)/(auth)/_handlers/use-sign-in.ts @@ -1,5 +1,5 @@ import { useNavigations } from "@/app/_hooks/use-navigations"; -import { useSignInMutation } from "../_queries/mutations"; +import { useSignInPasswordlessMutation } from "../_queries/mutations"; import { toast } from "sonner"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -7,7 +7,7 @@ import { ISignInPasswordlessSchema, SignInPasswordlessSchema } from "@/src/entit import { zodResolver } from "@hookform/resolvers/zod"; export function useSignInHandler() { - const { mutateAsync: signIn, isPending, error: errors } = useSignInMutation(); + const { mutateAsync: signIn, isPending, error: errors } = useSignInPasswordlessMutation(); const { router } = useNavigations(); const [error, setError] = useState(); diff --git a/sigap-website/app/(pages)/(auth)/_queries/mutations.ts b/sigap-website/app/(pages)/(auth)/_queries/mutations.ts index 9d4ee98..7eabde1 100644 --- a/sigap-website/app/(pages)/(auth)/_queries/mutations.ts +++ b/sigap-website/app/(pages)/(auth)/_queries/mutations.ts @@ -1,10 +1,17 @@ import { useMutation } from "@tanstack/react-query" -import { sendMagicLink, sendPasswordRecovery, signIn, signOut, verifyOtp } from "../action" +import { sendMagicLink, sendPasswordRecovery, signInPasswordless, signInWithPassword, signOut, verifyOtp } from "../action" -export const useSignInMutation = () => { +export const useSignInPasswordlessMutation = () => { return useMutation({ mutationKey: ["signIn"], - mutationFn: async (formData: FormData) => await signIn(formData), + mutationFn: async (formData: FormData) => await signInPasswordless(formData), + }) +} + +export const useSignInWithPasswordMutation = () => { + return useMutation({ + mutationKey: ["signInWithCredentials"], + mutationFn: async (formData: FormData) => await signInWithPassword(formData), }) } diff --git a/sigap-website/app/(pages)/(auth)/action.ts b/sigap-website/app/(pages)/(auth)/action.ts index 00dfc79..7077085 100644 --- a/sigap-website/app/(pages)/(auth)/action.ts +++ b/sigap-website/app/(pages)/(auth)/action.ts @@ -8,17 +8,18 @@ import { InputParseError, NotFoundError } 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) { +export async function signInPasswordless(formData: FormData) { const instrumentationService = getInjection("IInstrumentationService") return await instrumentationService.instrumentServerAction("signIn", { recordResponse: true }, async () => { - const email = formData.get("email")?.toString() try { - const signInController = getInjection("ISignInController") - return await signInController({ email }) + const email = formData.get("email")?.toString() + + const signInPasswordlessController = getInjection("ISignInPasswordlessController") + return await signInPasswordlessController({ email }) // if (email) { // redirect(`/verify-otp?email=${encodeURIComponent(email)}`) @@ -51,6 +52,43 @@ export async function signIn(formData: FormData) { }) } + +export async function signInWithPassword(formData: FormData) { + const instrumentationService = getInjection("IInstrumentationService") + return await instrumentationService.instrumentServerAction("signInWithPassword", { + recordResponse: true + }, async () => { + try { + const email = formData.get("email")?.toString() + const password = formData.get("password")?.toString() + + const signInWithPasswordController = getInjection("ISignInWithPasswordController") + return await signInWithPasswordController({ email, password }) + } catch (err) { + if (err instanceof InputParseError) { + return { error: err.message } + } + + if (err instanceof AuthenticationError) { + return { error: "Invalid credential. Please try again." } + } + + if (err instanceof UnauthenticatedError || err instanceof NotFoundError) { + return { + error: 'User not found. Please tell your admin to create an account for you.', + }; + } + + 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 () => { diff --git a/sigap-website/app/(pages)/(auth)/handler.tsx b/sigap-website/app/(pages)/(auth)/handler.tsx new file mode 100644 index 0000000..7817f5a --- /dev/null +++ b/sigap-website/app/(pages)/(auth)/handler.tsx @@ -0,0 +1,161 @@ +// import { AuthenticationError } from "@/src/entities/errors/auth"; +// import { useState } from "react"; +// import { useAuthActions } from './queries'; +// import { useForm } from 'react-hook-form'; +// import { zodResolver } from '@hookform/resolvers/zod';; +// import { toast } from 'sonner'; +// import { useNavigations } from '@/app/_hooks/use-navigations'; +// import { +// IVerifyOtpSchema, +// verifyOtpSchema, +// } from '@/src/entities/models/auth/verify-otp.model'; + +// /** +// * Hook untuk menangani proses sign in +// * +// * @returns {Object} Object berisi handler dan state untuk form sign in +// * @example +// * const { handleSubmit, isPending, error } = useSignInHandler(); +// *
...
+// */ +// export function useSignInHandler() { +// const { signIn } = useAuthActions(); +// const { router } = useNavigations(); + +// const [error, setError] = useState(); + +// const handleSubmit = async (event: React.FormEvent) => { +// event.preventDefault(); +// if (signIn.isPending) return; + +// setError(undefined); + +// const formData = new FormData(event.currentTarget); +// const email = formData.get('email')?.toString(); + +// const res = await signIn.mutateAsync(formData); + +// if (!res?.error) { +// toast('An email has been sent to you. Please check your inbox.'); +// if (email) router.push(`/verify-otp?email=${encodeURIComponent(email)}`); +// } else { +// setError(res.error); +// } + + +// }; + +// return { +// // formData, +// // handleChange, +// handleSignIn: handleSubmit, +// error, +// isPending: signIn.isPending, +// errors: !!error || signIn.error, +// clearError: () => setError(undefined), +// }; +// } + +// export function useVerifyOtpHandler(email: string) { +// const { router } = useNavigations(); +// const { verifyOtp } = useAuthActions(); +// const [error, setError] = useState(); + +// const { +// register, +// handleSubmit: hookFormSubmit, +// control, +// formState: { errors }, +// setValue, +// } = useForm({ +// resolver: zodResolver(verifyOtpSchema), +// defaultValues: { +// email, +// token: '', +// }, +// }); + +// const handleOtpChange = ( +// value: string, +// onChange: (value: string) => void +// ) => { +// onChange(value); + +// if (value.length === 6) { +// handleSubmit(); +// } + +// // Clear error when user starts typing +// if (error) { +// setError(undefined); +// } +// }; + +// const handleSubmit = hookFormSubmit(async (data) => { +// if (verifyOtp.isPending) return; + +// setError(undefined); + +// // Create FormData object +// const formData = new FormData(); +// formData.append('email', data.email); +// formData.append('token', data.token); + +// await verifyOtp.mutateAsync(formData, { +// onSuccess: () => { +// toast.success('OTP verified successfully'); +// // Navigate to dashboard on success +// router.push('/dashboard'); +// }, +// onError: (error) => { +// setError(error.message); +// }, +// }); +// }); + +// return { +// register, +// control, +// handleVerifyOtp: handleSubmit, +// handleOtpChange, +// errors: { +// ...errors, +// token: error ? { message: error } : errors.token, +// }, +// isPending: verifyOtp.isPending, +// clearError: () => setError(undefined), +// }; +// } + +// export function useSignOutHandler() { +// const { signOut } = useAuthActions(); +// const { router } = useNavigations(); +// const [error, setError] = useState(); + +// const handleSignOut = async () => { +// if (signOut.isPending) return; + +// setError(undefined); + +// await signOut.mutateAsync(undefined, { +// onSuccess: () => { +// toast.success('You have been signed out successfully'); +// router.push('/sign-in'); +// }, +// onError: (error) => { +// if (error instanceof AuthenticationError) { +// setError(error.message); +// toast.error(error.message); +// } +// }, +// }); +// }; + +// return { +// handleSignOut, +// error, +// isPending: signOut.isPending, +// errors: !!error || signOut.error, +// clearError: () => setError(undefined), +// }; +// } diff --git a/sigap-website/app/(pages)/(auth)/sign-in/_components/carousal-quote.tsx b/sigap-website/app/(pages)/(auth)/sign-in/_components/carousal-quote.tsx new file mode 100644 index 0000000..c943ca2 --- /dev/null +++ b/sigap-website/app/(pages)/(auth)/sign-in/_components/carousal-quote.tsx @@ -0,0 +1,50 @@ +import { Carousel, CarouselContent, CarouselItem } from "@/app/_components/ui/carousal" +import { QuoteIcon } from "lucide-react" + +export const CarousalQuotes = () => { + + const items = [ + { + quote: "Tried @supabase for the first time yesterday. Amazing tool! I was able to get my Posgres DB up in no time and their documentation on operating on the DB is super easy! 👏 Can't wait for Cloud functions to arrive! It's gonna be a great Firebase alternative!", + author: "@codewithbhargav", + image: "https://github.com/shadcn.png", + }, + { + quote: "Check out this amazing product @supabase. A must give try #newidea #opportunity", + author: "@techenthusiast", + image: "https://github.com/shadcn.png", + }, + { + quote: "Check out this amazing product @supabase. A must give try #newidea #opportunity", + author: "@dataguru", + image: "https://github.com/shadcn.png", + }, + ]; + + return ( + <> + + + {items.map((item, index) => ( + +
+ {/* */} +

{item.quote}

+
+ Profile +
+

{item.author}

+
+
+
+
+ ))} +
+
+ + ) +} \ No newline at end of file diff --git a/sigap-website/app/(pages)/(auth)/sign-in/page.tsx b/sigap-website/app/(pages)/(auth)/sign-in/page.tsx index 1f56e08..90a04e6 100644 --- a/sigap-website/app/(pages)/(auth)/sign-in/page.tsx +++ b/sigap-website/app/(pages)/(auth)/sign-in/page.tsx @@ -2,25 +2,9 @@ import { SignInForm } from "@/app/(pages)/(auth)/_components/signin-form"; import { Message } from "@/app/_components/form-message"; import { Button } from "@/app/_components/ui/button"; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/app/_components/ui/carousal"; +import { IconQuoteFilled } from "@tabler/icons-react"; import { GalleryVerticalEnd, Globe, QuoteIcon } from "lucide-react"; - -const carouselContent = [ - { - quote: "Tried @supabase for the first time yesterday. Amazing tool! I was able to get my Posgres DB up in no time and their documentation on operating on the DB is super easy! 👏 Can't wait for Cloud functions to arrive! It's gonna be a great Firebase alternative!", - author: "@codewithbhargav", - image: "https://github.com/shadcn.png", - }, - { - quote: "Check out this amazing product @supabase. A must give try #newidea #opportunity", - author: "@techenthusiast", - image: "https://github.com/shadcn.png", - }, - { - quote: "Check out this amazing product @supabase. A must give try #newidea #opportunity", - author: "@dataguru", - image: "https://github.com/shadcn.png", - }, -]; +import { CarousalQuotes } from "./_components/carousal-quote"; export default async function Login(props: { searchParams: Promise }) { return ( @@ -49,28 +33,7 @@ export default async function Login(props: { searchParams: Promise }) { Showcase - - - {carouselContent.map((item, index) => ( - -
- -

{item.quote}

-
- Profile -
-

{item.author}

-
-
-
-
- ))} -
-
+ ); diff --git a/sigap-website/di/modules/authentication.module.ts b/sigap-website/di/modules/authentication.module.ts index 8176ded..52a2d79 100644 --- a/sigap-website/di/modules/authentication.module.ts +++ b/sigap-website/di/modules/authentication.module.ts @@ -2,12 +2,12 @@ import { createModule } from '@evyweb/ioctopus'; import { AuthenticationService } from '@/src/infrastructure/services/authentication.service'; -import { signInUseCase } from '@/src/application/use-cases/auth/sign-in.use-case'; +import { signInUseCase } from '@/src/application/use-cases/auth/sign-in-passwordless.use-case'; import { signUpUseCase } from '@/src/application/use-cases/auth/sign-up.use-case'; import { signOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case'; import { DI_SYMBOLS } from '@/di/types'; -import { signInController } from '@/src/interface-adapters/controllers/auth/sign-in.controller'; +import { signInPasswordlessController } from '@/src/interface-adapters/controllers/auth/sign-in-passwordless.controller'; import { signOutController } from '@/src/interface-adapters/controllers/auth/sign-out.controller'; import { verifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case'; import { verifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller'; @@ -15,6 +15,8 @@ import { sendMagicLinkUseCase } from '@/src/application/use-cases/auth/send-magi import { sendPasswordRecoveryUseCase } from '@/src/application/use-cases/auth/send-password-recovery.use-case'; import { sendMagicLinkController } from '@/src/interface-adapters/controllers/auth/send-magic-link.controller'; import { sendPasswordRecoveryController } from '@/src/interface-adapters/controllers/auth/send-password-recovery.controller'; +import { signInWithPasswordUseCase } from '@/src/application/use-cases/auth/sign-in-with-password.use-case'; +import { signInWithPasswordController } from '@/src/interface-adapters/controllers/auth/sign-in-with-password.controller'; export function createAuthenticationModule() { const authenticationModule = createModule(); @@ -40,13 +42,21 @@ export function createAuthenticationModule() { // Use Cases authenticationModule - .bind(DI_SYMBOLS.ISignInUseCase) + .bind(DI_SYMBOLS.ISignInPasswordlessUseCase) .toHigherOrderFunction(signInUseCase, [ DI_SYMBOLS.IInstrumentationService, DI_SYMBOLS.IAuthenticationService, DI_SYMBOLS.IUsersRepository, ]); + authenticationModule + .bind(DI_SYMBOLS.ISignInWithPasswordUseCase) + .toHigherOrderFunction(signInWithPasswordUseCase, [ + DI_SYMBOLS.IInstrumentationService, + DI_SYMBOLS.IAuthenticationService, + DI_SYMBOLS.IUsersRepository, + ]); + authenticationModule .bind(DI_SYMBOLS.ISignUpUseCase) .toHigherOrderFunction(signUpUseCase, [ @@ -89,10 +99,17 @@ export function createAuthenticationModule() { // Controllers authenticationModule - .bind(DI_SYMBOLS.ISignInController) - .toHigherOrderFunction(signInController, [ + .bind(DI_SYMBOLS.ISignInPasswordlessController) + .toHigherOrderFunction(signInPasswordlessController, [ DI_SYMBOLS.IInstrumentationService, - DI_SYMBOLS.ISignInUseCase, + DI_SYMBOLS.ISignInPasswordlessUseCase, + ]); + + authenticationModule + .bind(DI_SYMBOLS.ISignInWithPasswordController) + .toHigherOrderFunction(signInWithPasswordController, [ + DI_SYMBOLS.IInstrumentationService, + DI_SYMBOLS.ISignInWithPasswordUseCase, ]); authenticationModule diff --git a/sigap-website/di/types.ts b/sigap-website/di/types.ts index 7e53b5d..2ed6fdc 100644 --- a/sigap-website/di/types.ts +++ b/sigap-website/di/types.ts @@ -3,12 +3,12 @@ import { ITransactionManagerService } from '@/src/application/services/transacti 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 { ISignInPasswordlessUseCase } from '@/src/application/use-cases/auth/sign-in-passwordless.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.interface'; import { IVerifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case'; -import { ISignInController } from '@/src/interface-adapters/controllers/auth/sign-in.controller'; +import { ISignInPasswordlessController } from '@/src/interface-adapters/controllers/auth/sign-in-passwordless.controller'; import { ISignOutController } from '@/src/interface-adapters/controllers/auth/sign-out.controller'; import { IVerifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller'; import { IBanUserUseCase } from '@/src/application/use-cases/users/ban-user.use-case'; @@ -39,6 +39,7 @@ import { ISendMagicLinkController } from '@/src/interface-adapters/controllers/a import { ISendPasswordRecoveryController } from '@/src/interface-adapters/controllers/auth/send-password-recovery.controller'; import { IUploadAvatarController } from '@/src/interface-adapters/controllers/users/upload-avatar.controller'; import { IUploadAvatarUseCase } from '@/src/application/use-cases/users/upload-avatar.use-case'; +import { ISignInWithPasswordController } from '@/src/interface-adapters/controllers/auth/sign-in-with-password.controller'; export const DI_SYMBOLS = { // Services @@ -51,7 +52,8 @@ export const DI_SYMBOLS = { IUsersRepository: Symbol.for('IUsersRepository'), // Use Cases - ISignInUseCase: Symbol.for('ISignInUseCase'), + ISignInPasswordlessUseCase: Symbol.for('ISignInPasswordlessUseCase'), + ISignInWithPasswordUseCase: Symbol.for('ISignInWithPasswordUseCase'), ISignUpUseCase: Symbol.for('ISignUpUseCase'), IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'), ISignOutUseCase: Symbol.for('ISignOutUseCase'), @@ -72,7 +74,8 @@ export const DI_SYMBOLS = { IUploadAvatarUseCase: Symbol.for('IUploadAvatarUseCase'), // Controllers - ISignInController: Symbol.for('ISignInController'), + ISignInPasswordlessController: Symbol.for('ISignInPasswordlessController'), + ISignInWithPasswordController: Symbol.for('ISignInWithPasswordController'), ISignOutController: Symbol.for('ISignOutController'), IVerifyOtpController: Symbol.for('IVerifyOtpController'), ISendMagicLinkController: Symbol.for('ISendMagicLinkController'), @@ -103,7 +106,7 @@ export interface DI_RETURN_TYPES { IUsersRepository: IUsersRepository; // Use Cases - ISignInUseCase: ISignInUseCase; + ISignInPasswordlessUseCase: ISignInPasswordlessUseCase; ISignUpUseCase: ISignUpUseCase; IVerifyOtpUseCase: IVerifyOtpUseCase; ISignOutUseCase: ISignOutUseCase; @@ -124,7 +127,8 @@ export interface DI_RETURN_TYPES { IUploadAvatarUseCase: IUploadAvatarUseCase; // Controllers - ISignInController: ISignInController; + ISignInPasswordlessController: ISignInPasswordlessController; + ISignInWithPasswordController: ISignInWithPasswordController; IVerifyOtpController: IVerifyOtpController; ISignOutController: ISignOutController; ISendMagicLinkController: ISendMagicLinkController; diff --git a/sigap-website/src/application/services/authentication.service.interface.ts b/sigap-website/src/application/services/authentication.service.interface.ts index 7c2d160..41512d1 100644 --- a/sigap-website/src/application/services/authentication.service.interface.ts +++ b/sigap-website/src/application/services/authentication.service.interface.ts @@ -9,9 +9,9 @@ import { IUserSchema } from "@/src/entities/models/users/users.model" export interface IAuthenticationService { signInPasswordless(credentials: ISignInPasswordlessSchema): Promise - SignInWithPasswordSchema(credentials: ISignInWithPasswordSchema): Promise - SignUpWithEmailSchema(credentials: ISignUpWithEmailSchema): Promise - SignUpWithPhoneSchema(credentials: ISignUpWithPhoneSchema): Promise + signInWithPassword(credentials: ISignInWithPasswordSchema): Promise + SignUpWithEmail(credentials: ISignUpWithEmailSchema): Promise + SignUpWithPhone(credentials: ISignUpWithPhoneSchema): Promise getSession(): Promise signOut(): Promise sendMagicLink(credentials: ISendMagicLinkSchema): Promise diff --git a/sigap-website/src/application/use-cases/auth/sign-in.use-case.ts b/sigap-website/src/application/use-cases/auth/sign-in-passwordless.use-case.ts similarity index 95% rename from sigap-website/src/application/use-cases/auth/sign-in.use-case.ts rename to sigap-website/src/application/use-cases/auth/sign-in-passwordless.use-case.ts index d6a7d64..6fe22da 100644 --- a/sigap-website/src/application/use-cases/auth/sign-in.use-case.ts +++ b/sigap-website/src/application/use-cases/auth/sign-in-passwordless.use-case.ts @@ -4,7 +4,7 @@ import { type TSignInSchema, ISignInPasswordlessSchema, SignInSchema } from "@/s import { IAuthenticationService } from "@/src/application/services/authentication.service.interface"; import { IUsersRepository } from "../../repositories/users.repository.interface"; -export type ISignInUseCase = ReturnType +export type ISignInPasswordlessUseCase = ReturnType export const signInUseCase = ( diff --git a/sigap-website/src/application/use-cases/auth/sign-in-with-password.use-case.ts b/sigap-website/src/application/use-cases/auth/sign-in-with-password.use-case.ts new file mode 100644 index 0000000..7a19537 --- /dev/null +++ b/sigap-website/src/application/use-cases/auth/sign-in-with-password.use-case.ts @@ -0,0 +1,27 @@ +import { UnauthenticatedError } from "@/src/entities/errors/auth"; +import { IUsersRepository } from "../../repositories/users.repository.interface"; +import { IAuthenticationService } from "../../services/authentication.service.interface"; +import { IInstrumentationService } from "../../services/instrumentation.service.interface"; + +export type ISignInWithPasswordUseCase = ReturnType + +export const signInWithPasswordUseCase = ( + instrumentationService: IInstrumentationService, + authenticationService: IAuthenticationService, + usersRepository: IUsersRepository +) => async (input: { email: string; password: string }): Promise => { + return await instrumentationService.startSpan({ name: "signInWithPassword Use Case", op: "function" }, + async () => { + + const existingUser = await usersRepository.getUserByEmail({ email: input.email }) + + if (!existingUser) { + throw new UnauthenticatedError("User not found. Please tell your admin to create an account for you.") + } + + await authenticationService.signInWithPassword({ email: input.email, password: input.password }) + + return + } + ) +} \ No newline at end of file diff --git a/sigap-website/src/entities/models/auth/sign-in.model.ts b/sigap-website/src/entities/models/auth/sign-in.model.ts index 55e9e27..da7be1d 100644 --- a/sigap-website/src/entities/models/auth/sign-in.model.ts +++ b/sigap-website/src/entities/models/auth/sign-in.model.ts @@ -16,7 +16,6 @@ export type TSignInSchema = z.infer; export const SignInWithPasswordSchema = SignInSchema.pick({ email: true, password: true, - phone: true }) @@ -26,7 +25,6 @@ export type ISignInWithPasswordSchema = z.infer export const defaulISignInWithPasswordSchemaValues: ISignInWithPasswordSchema = { email: "", password: "", - phone: "" }; export const SignInPasswordlessSchema = SignInSchema.pick({ @@ -40,9 +38,13 @@ export const defaulISignInPasswordlessSchemaValues: ISignInPasswordlessSchema = email: "", } + + // Define the sign-in response schema using Zod export const SignInResponseSchema = z.object({ success: z.boolean(), message: z.string(), redirectTo: z.string().optional(), }); + + diff --git a/sigap-website/src/infrastructure/services/authentication.service.ts b/sigap-website/src/infrastructure/services/authentication.service.ts index fb4f9ac..72a3a86 100644 --- a/sigap-website/src/infrastructure/services/authentication.service.ts +++ b/sigap-website/src/infrastructure/services/authentication.service.ts @@ -51,7 +51,7 @@ export class AuthenticationService implements IAuthenticationService { }) } - async SignInWithPasswordSchema(credentials: ISignInWithPasswordSchema): Promise { + async signInWithPassword(credentials: ISignInWithPasswordSchema): Promise { return await this.instrumentationService.startSpan({ name: "SignInWithPasswordSchema Use Case", }, async () => { @@ -79,7 +79,7 @@ export class AuthenticationService implements IAuthenticationService { }) } - async SignUpWithEmailSchema(credentials: ISignUpWithEmailSchema): Promise { + async SignUpWithEmail(credentials: ISignUpWithEmailSchema): Promise { return await this.instrumentationService.startSpan({ name: "SignUpWithEmailSchema Use Case", }, async () => { @@ -124,7 +124,7 @@ export class AuthenticationService implements IAuthenticationService { }) } - async SignUpWithPhoneSchema(credentials: ISignUpWithPhoneSchema): Promise { + async SignUpWithPhone(credentials: ISignUpWithPhoneSchema): Promise { throw new Error("Method not implemented."); } diff --git a/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.ts b/sigap-website/src/interface-adapters/controllers/auth/sign-in-passwordless.controller.ts similarity index 74% rename from sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.ts rename to sigap-website/src/interface-adapters/controllers/auth/sign-in-passwordless.controller.ts index 1463d61..80131fd 100644 --- a/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.ts +++ b/sigap-website/src/interface-adapters/controllers/auth/sign-in-passwordless.controller.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { ISignInUseCase } from "@/src/application/use-cases/auth/sign-in.use-case"; +import { ISignInPasswordlessUseCase } from "@/src/application/use-cases/auth/sign-in-passwordless.use-case"; import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; import { InputParseError } from "@/src/entities/errors/common"; @@ -8,12 +8,12 @@ const signInInputSchema = z.object({ email: z.string().min(1, "Email is Required").email("Please enter a valid email address"), }) -export type ISignInController = ReturnType +export type ISignInPasswordlessController = ReturnType -export const signInController = +export const signInPasswordlessController = ( instrumentationService: IInstrumentationService, - signInUseCase: ISignInUseCase + signInUseCase: ISignInPasswordlessUseCase ) => async (input: Partial>) => { return await instrumentationService.startSpan({ name: "signIn Controller" }, async () => { diff --git a/sigap-website/src/interface-adapters/controllers/auth/sign-in-with-password.controller.ts b/sigap-website/src/interface-adapters/controllers/auth/sign-in-with-password.controller.ts new file mode 100644 index 0000000..33a6417 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/auth/sign-in-with-password.controller.ts @@ -0,0 +1,34 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { ISignInWithPasswordUseCase } from "@/src/application/use-cases/auth/sign-in-with-password.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; + +import { z } from "zod"; + +// Sign In Controller +const signInWithPasswordInputSchema = z.object({ + email: z.string().min(1, "Email is Required").email("Please enter a valid email address"), + password: z.string().min(1, "Password is Required") +}) + +export type ISignInWithPasswordController = ReturnType + +export const signInWithPasswordController = ( + instrumentationService: IInstrumentationService, + signInWithPasswordUseCase: ISignInWithPasswordUseCase +) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "signInWithPassword Controller" }, + async () => { + const { data, error: inputParseError } = signInWithPasswordInputSchema.safeParse(input) + + if (inputParseError) { + throw new InputParseError(inputParseError.errors[0].message) + } + + return await signInWithPasswordUseCase({ + email: data.email, + password: data.password + }) + } + ) + } \ No newline at end of file