add sign in with password

This commit is contained in:
vergiLgood1 2025-04-03 22:54:34 +07:00
parent 741c44ebe5
commit 08ee186737
17 changed files with 379 additions and 76 deletions

View File

@ -9,7 +9,7 @@ import Link from "next/link";
import { FormField } from "@/app/_components/form-field"; import { FormField } from "@/app/_components/form-field";
// import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in.controller"; // import { useSignInController } from "@/src/interface-adapters/controllers/auth/sign-in.controller";
import { useState } from "react"; import { useState } from "react";
import { signIn } from "../action";
import { useSignInHandler } from "../_handlers/use-sign-in"; import { useSignInHandler } from "../_handlers/use-sign-in";
export function SignInForm({ export function SignInForm({
@ -98,7 +98,7 @@ export function SignInForm({
{isPending ? ( {isPending ? (
<> <>
<Loader2 className="h-5 w-5 animate-spin" /> <Loader2 className="h-5 w-5 animate-spin" />
Signing in... Sign in
</> </>
) : ( ) : (
"Sign in" "Sign in"

View File

@ -1,5 +1,5 @@
import { useNavigations } from "@/app/_hooks/use-navigations"; import { useNavigations } from "@/app/_hooks/use-navigations";
import { useSignInMutation } from "../_queries/mutations"; import { useSignInPasswordlessMutation } from "../_queries/mutations";
import { toast } from "sonner"; import { toast } from "sonner";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -7,7 +7,7 @@ import { ISignInPasswordlessSchema, SignInPasswordlessSchema } from "@/src/entit
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
export function useSignInHandler() { export function useSignInHandler() {
const { mutateAsync: signIn, isPending, error: errors } = useSignInMutation(); const { mutateAsync: signIn, isPending, error: errors } = useSignInPasswordlessMutation();
const { router } = useNavigations(); const { router } = useNavigations();
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();

View File

@ -1,10 +1,17 @@
import { useMutation } from "@tanstack/react-query" 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({ return useMutation({
mutationKey: ["signIn"], 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),
}) })
} }

View File

@ -8,17 +8,18 @@ import { InputParseError, NotFoundError } from "@/src/entities/errors/common"
import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth" import { AuthenticationError, UnauthenticatedError } from "@/src/entities/errors/auth"
import { createClient } from "@/app/_utils/supabase/server" import { createClient } from "@/app/_utils/supabase/server"
export async function signIn(formData: FormData) { export async function signInPasswordless(formData: FormData) {
const instrumentationService = getInjection("IInstrumentationService") const instrumentationService = getInjection("IInstrumentationService")
return await instrumentationService.instrumentServerAction("signIn", { return await instrumentationService.instrumentServerAction("signIn", {
recordResponse: true recordResponse: true
}, },
async () => { async () => {
const email = formData.get("email")?.toString()
try { try {
const signInController = getInjection("ISignInController") const email = formData.get("email")?.toString()
return await signInController({ email })
const signInPasswordlessController = getInjection("ISignInPasswordlessController")
return await signInPasswordlessController({ email })
// if (email) { // if (email) {
// redirect(`/verify-otp?email=${encodeURIComponent(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) { // export async function signUp(formData: FormData) {
// const instrumentationService = getInjection("IInstrumentationService") // const instrumentationService = getInjection("IInstrumentationService")
// return await instrumentationService.instrumentServerAction("signUp", { recordResponse: true }, async () => { // return await instrumentationService.instrumentServerAction("signUp", { recordResponse: true }, async () => {

View File

@ -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();
// * <form onSubmit={handleSubmit}>...</form>
// */
// export function useSignInHandler() {
// const { signIn } = useAuthActions();
// const { router } = useNavigations();
// const [error, setError] = useState<string>();
// const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// 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<string>();
// const {
// register,
// handleSubmit: hookFormSubmit,
// control,
// formState: { errors },
// setValue,
// } = useForm<IVerifyOtpSchema>({
// 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<string>();
// 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),
// };
// }

View File

@ -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 (
<>
<Carousel showDots autoPlay autoPlayInterval={10000} className="w-full max-w-md" >
<CarouselContent className="py-8">
{items.map((item, index) => (
<CarouselItem key={index} className="flex flex-col items-center justify-center">
<div className="relative flex flex-col items-start text-start">
{/* <QuoteIcon className="absolute h-20 w-20 text-muted opacity-80 -z-10 top-[-30px] transform rotate-180 " /> */}
<h2 className="text-3xl font-medium text-white mb-8">{item.quote}</h2>
<div className="flex items-center gap-4">
<img
src={item.image}
alt="Profile"
className="w-12 h-12 rounded-full"
/>
<div>
<p className="text-white font-medium">{item.author}</p>
</div>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</>
)
}

View File

@ -2,25 +2,9 @@ import { SignInForm } from "@/app/(pages)/(auth)/_components/signin-form";
import { Message } from "@/app/_components/form-message"; import { Message } from "@/app/_components/form-message";
import { Button } from "@/app/_components/ui/button"; import { Button } from "@/app/_components/ui/button";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/app/_components/ui/carousal"; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/app/_components/ui/carousal";
import { IconQuoteFilled } from "@tabler/icons-react";
import { GalleryVerticalEnd, Globe, QuoteIcon } from "lucide-react"; import { GalleryVerticalEnd, Globe, QuoteIcon } from "lucide-react";
import { CarousalQuotes } from "./_components/carousal-quote";
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",
},
];
export default async function Login(props: { searchParams: Promise<Message> }) { export default async function Login(props: { searchParams: Promise<Message> }) {
return ( return (
@ -49,28 +33,7 @@ export default async function Login(props: { searchParams: Promise<Message> }) {
<Globe className="mr-0 h-4 w-4" /> <Globe className="mr-0 h-4 w-4" />
Showcase Showcase
</Button> </Button>
<Carousel showDots autoPlay autoPlayInterval={10000} className="w-full max-w-md" > <CarousalQuotes />
<CarouselContent className="py-8">
{carouselContent.map((item, index) => (
<CarouselItem key={index} className="flex flex-col items-center justify-center">
<div className="relative flex flex-col items-start text-start">
<QuoteIcon className="absolute h-20 w-20 text-primary opacity-10 -z-10 top-[-30px] transform rotate-180 " />
<h2 className="text-3xl font-medium text-white mb-8">{item.quote}</h2>
<div className="flex items-center gap-4">
<img
src={item.image}
alt="Profile"
className="w-12 h-12 rounded-full"
/>
<div>
<p className="text-white font-medium">{item.author}</p>
</div>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div> </div>
</div> </div>
); );

View File

@ -2,12 +2,12 @@ import { createModule } from '@evyweb/ioctopus';
import { AuthenticationService } from '@/src/infrastructure/services/authentication.service'; 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 { signUpUseCase } from '@/src/application/use-cases/auth/sign-up.use-case';
import { signOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case'; import { signOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case';
import { DI_SYMBOLS } from '@/di/types'; 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 { signOutController } from '@/src/interface-adapters/controllers/auth/sign-out.controller';
import { verifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case'; import { verifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case';
import { verifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller'; 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 { 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 { sendMagicLinkController } from '@/src/interface-adapters/controllers/auth/send-magic-link.controller';
import { sendPasswordRecoveryController } from '@/src/interface-adapters/controllers/auth/send-password-recovery.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() { export function createAuthenticationModule() {
const authenticationModule = createModule(); const authenticationModule = createModule();
@ -40,13 +42,21 @@ export function createAuthenticationModule() {
// Use Cases // Use Cases
authenticationModule authenticationModule
.bind(DI_SYMBOLS.ISignInUseCase) .bind(DI_SYMBOLS.ISignInPasswordlessUseCase)
.toHigherOrderFunction(signInUseCase, [ .toHigherOrderFunction(signInUseCase, [
DI_SYMBOLS.IInstrumentationService, DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.IAuthenticationService, DI_SYMBOLS.IAuthenticationService,
DI_SYMBOLS.IUsersRepository, DI_SYMBOLS.IUsersRepository,
]); ]);
authenticationModule
.bind(DI_SYMBOLS.ISignInWithPasswordUseCase)
.toHigherOrderFunction(signInWithPasswordUseCase, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.IAuthenticationService,
DI_SYMBOLS.IUsersRepository,
]);
authenticationModule authenticationModule
.bind(DI_SYMBOLS.ISignUpUseCase) .bind(DI_SYMBOLS.ISignUpUseCase)
.toHigherOrderFunction(signUpUseCase, [ .toHigherOrderFunction(signUpUseCase, [
@ -89,10 +99,17 @@ export function createAuthenticationModule() {
// Controllers // Controllers
authenticationModule authenticationModule
.bind(DI_SYMBOLS.ISignInController) .bind(DI_SYMBOLS.ISignInPasswordlessController)
.toHigherOrderFunction(signInController, [ .toHigherOrderFunction(signInPasswordlessController, [
DI_SYMBOLS.IInstrumentationService, DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ISignInUseCase, DI_SYMBOLS.ISignInPasswordlessUseCase,
]);
authenticationModule
.bind(DI_SYMBOLS.ISignInWithPasswordController)
.toHigherOrderFunction(signInWithPasswordController, [
DI_SYMBOLS.IInstrumentationService,
DI_SYMBOLS.ISignInWithPasswordUseCase,
]); ]);
authenticationModule authenticationModule

View File

@ -3,12 +3,12 @@ import { ITransactionManagerService } from '@/src/application/services/transacti
import { IInstrumentationService } from '@/src/application/services/instrumentation.service.interface'; import { IInstrumentationService } from '@/src/application/services/instrumentation.service.interface';
import { ICrashReporterService } from '@/src/application/services/crash-reporter.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 { ISignUpUseCase } from '@/src/application/use-cases/auth/sign-up.use-case';
import { ISignOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case'; import { ISignOutUseCase } from '@/src/application/use-cases/auth/sign-out.use-case';
import { IUsersRepository } from '@/src/application/repositories/users.repository.interface'; import { IUsersRepository } from '@/src/application/repositories/users.repository.interface';
import { IVerifyOtpUseCase } from '@/src/application/use-cases/auth/verify-otp.use-case'; 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 { ISignOutController } from '@/src/interface-adapters/controllers/auth/sign-out.controller';
import { IVerifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller'; import { IVerifyOtpController } from '@/src/interface-adapters/controllers/auth/verify-otp.controller';
import { IBanUserUseCase } from '@/src/application/use-cases/users/ban-user.use-case'; 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 { ISendPasswordRecoveryController } from '@/src/interface-adapters/controllers/auth/send-password-recovery.controller';
import { IUploadAvatarController } from '@/src/interface-adapters/controllers/users/upload-avatar.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 { 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 = { export const DI_SYMBOLS = {
// Services // Services
@ -51,7 +52,8 @@ export const DI_SYMBOLS = {
IUsersRepository: Symbol.for('IUsersRepository'), IUsersRepository: Symbol.for('IUsersRepository'),
// Use Cases // Use Cases
ISignInUseCase: Symbol.for('ISignInUseCase'), ISignInPasswordlessUseCase: Symbol.for('ISignInPasswordlessUseCase'),
ISignInWithPasswordUseCase: Symbol.for('ISignInWithPasswordUseCase'),
ISignUpUseCase: Symbol.for('ISignUpUseCase'), ISignUpUseCase: Symbol.for('ISignUpUseCase'),
IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'), IVerifyOtpUseCase: Symbol.for('IVerifyOtpUseCase'),
ISignOutUseCase: Symbol.for('ISignOutUseCase'), ISignOutUseCase: Symbol.for('ISignOutUseCase'),
@ -72,7 +74,8 @@ export const DI_SYMBOLS = {
IUploadAvatarUseCase: Symbol.for('IUploadAvatarUseCase'), IUploadAvatarUseCase: Symbol.for('IUploadAvatarUseCase'),
// Controllers // Controllers
ISignInController: Symbol.for('ISignInController'), ISignInPasswordlessController: Symbol.for('ISignInPasswordlessController'),
ISignInWithPasswordController: Symbol.for('ISignInWithPasswordController'),
ISignOutController: Symbol.for('ISignOutController'), ISignOutController: Symbol.for('ISignOutController'),
IVerifyOtpController: Symbol.for('IVerifyOtpController'), IVerifyOtpController: Symbol.for('IVerifyOtpController'),
ISendMagicLinkController: Symbol.for('ISendMagicLinkController'), ISendMagicLinkController: Symbol.for('ISendMagicLinkController'),
@ -103,7 +106,7 @@ export interface DI_RETURN_TYPES {
IUsersRepository: IUsersRepository; IUsersRepository: IUsersRepository;
// Use Cases // Use Cases
ISignInUseCase: ISignInUseCase; ISignInPasswordlessUseCase: ISignInPasswordlessUseCase;
ISignUpUseCase: ISignUpUseCase; ISignUpUseCase: ISignUpUseCase;
IVerifyOtpUseCase: IVerifyOtpUseCase; IVerifyOtpUseCase: IVerifyOtpUseCase;
ISignOutUseCase: ISignOutUseCase; ISignOutUseCase: ISignOutUseCase;
@ -124,7 +127,8 @@ export interface DI_RETURN_TYPES {
IUploadAvatarUseCase: IUploadAvatarUseCase; IUploadAvatarUseCase: IUploadAvatarUseCase;
// Controllers // Controllers
ISignInController: ISignInController; ISignInPasswordlessController: ISignInPasswordlessController;
ISignInWithPasswordController: ISignInWithPasswordController;
IVerifyOtpController: IVerifyOtpController; IVerifyOtpController: IVerifyOtpController;
ISignOutController: ISignOutController; ISignOutController: ISignOutController;
ISendMagicLinkController: ISendMagicLinkController; ISendMagicLinkController: ISendMagicLinkController;

View File

@ -9,9 +9,9 @@ import { IUserSchema } from "@/src/entities/models/users/users.model"
export interface IAuthenticationService { export interface IAuthenticationService {
signInPasswordless(credentials: ISignInPasswordlessSchema): Promise<void> signInPasswordless(credentials: ISignInPasswordlessSchema): Promise<void>
SignInWithPasswordSchema(credentials: ISignInWithPasswordSchema): Promise<void> signInWithPassword(credentials: ISignInWithPasswordSchema): Promise<void>
SignUpWithEmailSchema(credentials: ISignUpWithEmailSchema): Promise<IUserSchema> SignUpWithEmail(credentials: ISignUpWithEmailSchema): Promise<IUserSchema>
SignUpWithPhoneSchema(credentials: ISignUpWithPhoneSchema): Promise<IUserSchema> SignUpWithPhone(credentials: ISignUpWithPhoneSchema): Promise<IUserSchema>
getSession(): Promise<Session | null> getSession(): Promise<Session | null>
signOut(): Promise<void> signOut(): Promise<void>
sendMagicLink(credentials: ISendMagicLinkSchema): Promise<void> sendMagicLink(credentials: ISendMagicLinkSchema): Promise<void>

View File

@ -4,7 +4,7 @@ import { type TSignInSchema, ISignInPasswordlessSchema, SignInSchema } from "@/s
import { IAuthenticationService } from "@/src/application/services/authentication.service.interface"; import { IAuthenticationService } from "@/src/application/services/authentication.service.interface";
import { IUsersRepository } from "../../repositories/users.repository.interface"; import { IUsersRepository } from "../../repositories/users.repository.interface";
export type ISignInUseCase = ReturnType<typeof signInUseCase> export type ISignInPasswordlessUseCase = ReturnType<typeof signInUseCase>
export const signInUseCase = export const signInUseCase =
( (

View File

@ -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<typeof signInWithPasswordUseCase>
export const signInWithPasswordUseCase = (
instrumentationService: IInstrumentationService,
authenticationService: IAuthenticationService,
usersRepository: IUsersRepository
) => async (input: { email: string; password: string }): Promise<void> => {
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
}
)
}

View File

@ -16,7 +16,6 @@ export type TSignInSchema = z.infer<typeof SignInSchema>;
export const SignInWithPasswordSchema = SignInSchema.pick({ export const SignInWithPasswordSchema = SignInSchema.pick({
email: true, email: true,
password: true, password: true,
phone: true
}) })
@ -26,7 +25,6 @@ export type ISignInWithPasswordSchema = z.infer<typeof SignInWithPasswordSchema>
export const defaulISignInWithPasswordSchemaValues: ISignInWithPasswordSchema = { export const defaulISignInWithPasswordSchemaValues: ISignInWithPasswordSchema = {
email: "", email: "",
password: "", password: "",
phone: ""
}; };
export const SignInPasswordlessSchema = SignInSchema.pick({ export const SignInPasswordlessSchema = SignInSchema.pick({
@ -40,9 +38,13 @@ export const defaulISignInPasswordlessSchemaValues: ISignInPasswordlessSchema =
email: "", email: "",
} }
// Define the sign-in response schema using Zod // Define the sign-in response schema using Zod
export const SignInResponseSchema = z.object({ export const SignInResponseSchema = z.object({
success: z.boolean(), success: z.boolean(),
message: z.string(), message: z.string(),
redirectTo: z.string().optional(), redirectTo: z.string().optional(),
}); });

View File

@ -51,7 +51,7 @@ export class AuthenticationService implements IAuthenticationService {
}) })
} }
async SignInWithPasswordSchema(credentials: ISignInWithPasswordSchema): Promise<void> { async signInWithPassword(credentials: ISignInWithPasswordSchema): Promise<void> {
return await this.instrumentationService.startSpan({ return await this.instrumentationService.startSpan({
name: "SignInWithPasswordSchema Use Case", name: "SignInWithPasswordSchema Use Case",
}, async () => { }, async () => {
@ -79,7 +79,7 @@ export class AuthenticationService implements IAuthenticationService {
}) })
} }
async SignUpWithEmailSchema(credentials: ISignUpWithEmailSchema): Promise<IUserSchema> { async SignUpWithEmail(credentials: ISignUpWithEmailSchema): Promise<IUserSchema> {
return await this.instrumentationService.startSpan({ return await this.instrumentationService.startSpan({
name: "SignUpWithEmailSchema Use Case", name: "SignUpWithEmailSchema Use Case",
}, async () => { }, async () => {
@ -124,7 +124,7 @@ export class AuthenticationService implements IAuthenticationService {
}) })
} }
async SignUpWithPhoneSchema(credentials: ISignUpWithPhoneSchema): Promise<IUserSchema> { async SignUpWithPhone(credentials: ISignUpWithPhoneSchema): Promise<IUserSchema> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }

View File

@ -1,5 +1,5 @@
import { z } from "zod"; 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 { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface";
import { InputParseError } from "@/src/entities/errors/common"; 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"), email: z.string().min(1, "Email is Required").email("Please enter a valid email address"),
}) })
export type ISignInController = ReturnType<typeof signInController> export type ISignInPasswordlessController = ReturnType<typeof signInPasswordlessController>
export const signInController = export const signInPasswordlessController =
( (
instrumentationService: IInstrumentationService, instrumentationService: IInstrumentationService,
signInUseCase: ISignInUseCase signInUseCase: ISignInPasswordlessUseCase
) => ) =>
async (input: Partial<z.infer<typeof signInInputSchema>>) => { async (input: Partial<z.infer<typeof signInInputSchema>>) => {
return await instrumentationService.startSpan({ name: "signIn Controller" }, async () => { return await instrumentationService.startSpan({ name: "signIn Controller" }, async () => {

View File

@ -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<typeof signInWithPasswordController>
export const signInWithPasswordController = (
instrumentationService: IInstrumentationService,
signInWithPasswordUseCase: ISignInWithPasswordUseCase
) =>
async (input: Partial<z.infer<typeof signInWithPasswordInputSchema>>) => {
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
})
}
)
}