From 4750f0055ebdf9434520ae19fc1eb124ef818876 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Wed, 19 Mar 2025 06:08:32 +0700 Subject: [PATCH] Membuat use case dan controller untuk users repository --- .../_components/navigations/nav-main.tsx | 28 +-- .../_components/navigations/nav-pre-main.tsx | 4 +- .../(admin)/dashboard/welcome/page.tsx | 9 + .../(auth)/_components/signin-form.tsx | 4 +- .../(auth)/_components/verify-otp-form.tsx | 4 +- sigap-website/app/(pages)/(auth)/action.ts | 1 + sigap-website/app/(pages)/(auth)/handler.tsx | 4 +- sigap-website/app/(pages)/(auth)/mutation.ts | 14 +- sigap-website/di/modules/users.module.ts | 2 +- .../users.repository.interface.ts | 15 +- .../use-cases/users/ban-user.use-case.ts | 4 +- .../use-cases/users/create-user.use-case.ts | 9 +- .../use-cases/users/delete-user.use-case.ts | 14 +- .../users/get-current-user.use-case.ts | 9 +- .../users/get-list-users.use-case.ts | 4 +- .../users/get-user-by-email.use-case.ts | 8 +- .../users/get-user-by-id.use-case.ts | 8 +- .../users/get-user-by-username.use-case.ts | 8 +- .../use-cases/users/invite-user.use-case.ts | 12 +- .../use-cases/users/unban-user.use-case.ts | 27 +++ .../use-cases/users/update-user.use-case.ts | 6 +- ...repository.impl.ts => users.repository.ts} | 163 +++++++++------ .../controllers/auth/sign-in.controller.tsx | 193 ------------------ .../auth/verify-otp.controller.tsx | 99 --------- .../controllers/users/ban-user.controller.ts | 27 +++ .../users/create-user.controller.ts | 33 +++ .../users/delete-user.controller.ts | 25 +++ .../users/get-current-user.controller.ts | 18 ++ .../users/get-list-users.controller.ts | 15 ++ .../users/get-user-by-email.controller.ts | 27 +++ .../users/get-user-by-id.controller.ts | 26 +++ .../users/get-user-by-username.controller.ts | 27 +++ .../users/invite-user.controller.ts | 28 +++ .../users/unban-user.controller.ts | 26 +++ .../users/update-user-controller.ts | 36 ++++ sigap-website/tailwind.config.ts | 10 + 36 files changed, 506 insertions(+), 441 deletions(-) create mode 100644 sigap-website/app/(pages)/(admin)/dashboard/welcome/page.tsx create mode 100644 sigap-website/src/application/use-cases/users/unban-user.use-case.ts rename sigap-website/src/infrastructure/repositories/{users.repository.impl.ts => users.repository.ts} (84%) create mode 100644 sigap-website/src/interface-adapters/controllers/users/ban-user.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/create-user.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/delete-user.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/get-current-user.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/get-list-users.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/get-user-by-email.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/get-user-by-id.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/get-user-by-username.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/invite-user.controller.ts create mode 100644 sigap-website/src/interface-adapters/controllers/users/unban-user.controller.ts diff --git a/sigap-website/app/(pages)/(admin)/_components/navigations/nav-main.tsx b/sigap-website/app/(pages)/(admin)/_components/navigations/nav-main.tsx index 726fa10..48ae392 100644 --- a/sigap-website/app/(pages)/(admin)/_components/navigations/nav-main.tsx +++ b/sigap-website/app/(pages)/(admin)/_components/navigations/nav-main.tsx @@ -52,11 +52,11 @@ function SubSubItemComponent({ item }: { item: SubSubItem }) { asChild className={ isActive - ? "font-medium bg-primary/10 before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:bg-primary" + ? "bg-primary/10 active text-primary" : "" } > - + {item.title} @@ -77,13 +77,13 @@ function SubItemComponent({ item }: { item: SubItem }) { asChild className={ isActive - ? "font-medium bg-primary/10 before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:bg-primary" + ? "bg-primary/10 active text-primary" : "" } > - + {item.icon && ( - + )} {item.title} @@ -99,14 +99,14 @@ function SubItemComponent({ item }: { item: SubItem }) { {item.icon && ( - + )} - {item.title} + {item.title} @@ -138,13 +138,13 @@ function RecursiveNavItem({ item, index }: { item: NavItem; index: number }) { asChild className={ isActive - ? "font-medium bg-primary/10 before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:bg-primary" + ? "bg-primary/10 active text-primary" : "" } > - + {item.icon && ( - + )} {item.title} @@ -166,14 +166,14 @@ function RecursiveNavItem({ item, index }: { item: NavItem; index: number }) { tooltip={item.title} className={ isActive - ? "font-medium bg-primary/10 before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:bg-primary" + ? "bg-primary/10 active text-primary" : "" } > {item.icon && ( - + )} - {item.title} + {item.title} {hasSubItems && ( - + + {item.title} diff --git a/sigap-website/app/(pages)/(admin)/dashboard/welcome/page.tsx b/sigap-website/app/(pages)/(admin)/dashboard/welcome/page.tsx new file mode 100644 index 0000000..fd25940 --- /dev/null +++ b/sigap-website/app/(pages)/(admin)/dashboard/welcome/page.tsx @@ -0,0 +1,9 @@ +const WelcomePage = () => { + return ( + <> +

Welcome to the dashboard

+ + ); +} + +export default WelcomePage; \ No newline at end of file diff --git a/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx b/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx index 16b61eb..df18463 100644 --- a/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx +++ b/sigap-website/app/(pages)/(auth)/_components/signin-form.tsx @@ -33,7 +33,7 @@ export function SignInForm({ // setLoading(false); // }; - const { isPending, handleSubmit, error, errors, clearError } = useSignInHandler(); + const { isPending, handleSignIn, error, errors, clearError } = useSignInHandler(); return (
@@ -76,7 +76,7 @@ export function SignInForm({
-
+ - +
{ - const email = formData.get("email")?.toString() const response = await signIn(formData); // If the server action returns an error, treat it as an error for React Query if (response?.error) { throw new Error(response.error); } - - return { email }; } }); const verifyOtpMutation = useMutation({ + mutationKey: ["verifyOtp"], mutationFn: async (formData: FormData) => { - const email = formData.get("email")?.toString() - const token = formData.get("token")?.toString() const response = await verifyOtp(formData); // If the server action returns an error, treat it as an error for React Query if (response?.error) { throw new Error(response.error); } - - return { email, token }; } }) const signOutMutation = useMutation({ + mutationKey: ["signOut"], mutationFn: async () => { const response = await signOut(); @@ -40,8 +36,6 @@ export function useAuthActions() { if (response?.error) { throw new Error(response.error); } - - return response; } }) diff --git a/sigap-website/di/modules/users.module.ts b/sigap-website/di/modules/users.module.ts index 7e4f325..40cbc71 100644 --- a/sigap-website/di/modules/users.module.ts +++ b/sigap-website/di/modules/users.module.ts @@ -2,7 +2,7 @@ import { createModule } from '@evyweb/ioctopus'; import { DI_SYMBOLS } from '@/di/types'; -import { UsersRepository } from '@/src/infrastructure/repositories/users.repository.impl'; +import { UsersRepository } from '@/src/infrastructure/repositories/users.repository'; export function createUsersModule() { diff --git a/sigap-website/src/application/repositories/users.repository.interface.ts b/sigap-website/src/application/repositories/users.repository.interface.ts index 6621d85..07a846b 100644 --- a/sigap-website/src/application/repositories/users.repository.interface.ts +++ b/sigap-website/src/application/repositories/users.repository.interface.ts @@ -1,23 +1,18 @@ import { createAdminClient } from "@/app/_utils/supabase/admin"; import { createClient } from "@/app/_utils/supabase/client"; import { CreateUser, InviteUser, UpdateUser, User, UserResponse } from "@/src/entities/models/users/users.model"; -import db from "@/prisma/db"; -import { DatabaseOperationError, NotFoundError } from "@/src/entities/errors/common"; -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 { ITransaction } from "@/src/entities/models/transaction.interface"; export interface IUsersRepository { listUsers(): Promise; - getCurrentUser(): Promise; + getCurrentUser(): Promise; getUserById(id: string): Promise; getUserByUsername(username: string): Promise; getUserByEmail(email: string): Promise; - createUser(input: CreateUser, tx?: ITransaction): Promise; - inviteUser(email: string, tx?: ITransaction): Promise; - updateUser(id: string, input: Partial, tx?: ITransaction): Promise; - deleteUser(id: string, tx?: ITransaction): Promise; + createUser(input: CreateUser, tx?: ITransaction): Promise; + inviteUser(email: string, tx?: ITransaction): Promise; + updateUser(id: string, input: Partial, tx?: ITransaction): Promise; + deleteUser(id: string, tx?: ITransaction): Promise; banUser(id: string, ban_duration: string, tx?: ITransaction): Promise; unbanUser(id: string, tx?: ITransaction): Promise; } diff --git a/sigap-website/src/application/use-cases/users/ban-user.use-case.ts b/sigap-website/src/application/use-cases/users/ban-user.use-case.ts index fc2ddeb..2800a76 100644 --- a/sigap-website/src/application/use-cases/users/ban-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/ban-user.use-case.ts @@ -19,9 +19,7 @@ export const banUserUseCase = ( const bannedUser = await usersRepository.banUser(id, ban_duration) - return { - ...bannedUser - } + return bannedUser } ) } \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/create-user.use-case.ts b/sigap-website/src/application/use-cases/users/create-user.use-case.ts index cbc73f0..d6c3a7e 100644 --- a/sigap-website/src/application/use-cases/users/create-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/create-user.use-case.ts @@ -14,6 +14,7 @@ export const createUserUseCase = ( ) => async (input: CreateUser): Promise => { return await instrumentationService.startSpan({ name: "createUser Use Case", op: "function" }, async () => { + const existingUser = await usersRepository.getUserByEmail(input.email) if (existingUser) { @@ -26,13 +27,7 @@ export const createUserUseCase = ( email_confirm: true }) - if (!newUser) { - throw new InputParseError("User not created") - } - - return { - ...newUser - }; + return newUser } ) } diff --git a/sigap-website/src/application/use-cases/users/delete-user.use-case.ts b/sigap-website/src/application/use-cases/users/delete-user.use-case.ts index 6c07f0b..97a9829 100644 --- a/sigap-website/src/application/use-cases/users/delete-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/delete-user.use-case.ts @@ -1,15 +1,25 @@ +import { NotFoundError } from "@/src/entities/errors/common" import { IUsersRepository } from "../../repositories/users.repository.interface" import { IInstrumentationService } from "../../services/instrumentation.service.interface" +import { User } from "@/src/entities/models/users/users.model" export type IDeleteUserUseCase = ReturnType const deleteUserUseCase = ( instrumentationService: IInstrumentationService, usersRepository: IUsersRepository -) => async (id: string): Promise => { +) => async (id: string): Promise => { return await instrumentationService.startSpan({ name: "deleteUser Use Case", op: "function" }, async () => { - await usersRepository.deleteUser(id) + const user = await usersRepository.getUserById(id) + + if (!user) { + throw new NotFoundError("User not found") + } + + const deletedUser = await usersRepository.deleteUser(id) + + return deletedUser } ) } \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/get-current-user.use-case.ts b/sigap-website/src/application/use-cases/users/get-current-user.use-case.ts index 8c3bbff..758dae6 100644 --- a/sigap-website/src/application/use-cases/users/get-current-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/get-current-user.use-case.ts @@ -1,7 +1,8 @@ import { NotFoundError } from "@/src/entities/errors/common" import { IUsersRepository } from "../../repositories/users.repository.interface" import { IInstrumentationService } from "../../services/instrumentation.service.interface" -import { UserResponse } from "@/src/entities/models/users/users.model" +import { User, UserResponse } from "@/src/entities/models/users/users.model" +import { AuthenticationError } from "@/src/entities/errors/auth" export type IGetCurrentUserUseCase = ReturnType @@ -9,7 +10,7 @@ export type IGetCurrentUserUseCase = ReturnType export const getCurrentUserUseCase = ( instrumentationService: IInstrumentationService, usersRepository: IUsersRepository -) => async (): Promise => { +) => async (): Promise => { return await instrumentationService.startSpan({ name: "getCurrentUser Use Case", op: "function" }, async () => { @@ -19,9 +20,7 @@ export const getCurrentUserUseCase = ( throw new NotFoundError("User not found") } - return { - ...existingUser - } + return existingUser } ) } diff --git a/sigap-website/src/application/use-cases/users/get-list-users.use-case.ts b/sigap-website/src/application/use-cases/users/get-list-users.use-case.ts index 15ad276..ba68ec2 100644 --- a/sigap-website/src/application/use-cases/users/get-list-users.use-case.ts +++ b/sigap-website/src/application/use-cases/users/get-list-users.use-case.ts @@ -10,7 +10,9 @@ export const getListUsersUseCase = ( ) => async (): Promise => { return await instrumentationService.startSpan({ name: "getListUsers Use Case", op: "function" }, async () => { - return await usersRepository.listUsers() + const users = await usersRepository.listUsers() + + return users } ) } \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/get-user-by-email.use-case.ts b/sigap-website/src/application/use-cases/users/get-user-by-email.use-case.ts index 7cdafbc..e6ef08a 100644 --- a/sigap-website/src/application/use-cases/users/get-user-by-email.use-case.ts +++ b/sigap-website/src/application/use-cases/users/get-user-by-email.use-case.ts @@ -12,15 +12,13 @@ const getUserByEmailUseCase = ( return await instrumentationService.startSpan({ name: "getUserByEmail Use Case", op: "function" }, async () => { - const existingUser = await usersRepository.getUserByEmail(email) + const user = await usersRepository.getUserByEmail(email) - if (!existingUser) { + if (!user) { throw new NotFoundError("User not found") } - return { - ...existingUser - } + return user } ) } diff --git a/sigap-website/src/application/use-cases/users/get-user-by-id.use-case.ts b/sigap-website/src/application/use-cases/users/get-user-by-id.use-case.ts index bed2636..54d9816 100644 --- a/sigap-website/src/application/use-cases/users/get-user-by-id.use-case.ts +++ b/sigap-website/src/application/use-cases/users/get-user-by-id.use-case.ts @@ -14,15 +14,13 @@ export const getUserByIdUseCase = ( return await instrumentationService.startSpan({ name: "getUserById Use Case", op: "function" }, async () => { - const existingUser = await usersRepository.getUserById(id) + const user = await usersRepository.getUserById(id) - if (!existingUser) { + if (!user) { throw new NotFoundError("User not found") } - return { - ...existingUser - } + return user } ) } \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/get-user-by-username.use-case.ts b/sigap-website/src/application/use-cases/users/get-user-by-username.use-case.ts index 10c8787..35b1630 100644 --- a/sigap-website/src/application/use-cases/users/get-user-by-username.use-case.ts +++ b/sigap-website/src/application/use-cases/users/get-user-by-username.use-case.ts @@ -12,15 +12,13 @@ const getUserByUsernameUseCase = ( return await instrumentationService.startSpan({ name: "getUserByUsername Use Case", op: "function" }, async () => { - const existingUser = await usersRepository.getUserByUsername(username) + const user = await usersRepository.getUserByUsername(username) - if (!existingUser) { + if (!user) { throw new NotFoundError("User not found") } - return { - ...existingUser - } + return user } ) } \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/invite-user.use-case.ts b/sigap-website/src/application/use-cases/users/invite-user.use-case.ts index 72e79fe..146e100 100644 --- a/sigap-website/src/application/use-cases/users/invite-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/invite-user.use-case.ts @@ -11,24 +11,22 @@ export const inviteUserUseCase = ( instrumentationService: IInstrumentationService, usersRepository: IUsersRepository, authenticationService: IAuthenticationService, -) => async (input: { email: string }): Promise => { +) => async (email: string): Promise => { return await instrumentationService.startSpan({ name: "inviteUser Use Case", op: "function" }, async () => { - const existingUser = await usersRepository.getUserByEmail(input.email) + const existingUser = await usersRepository.getUserByEmail(email) if (existingUser) { throw new AuthenticationError("User already exists") } - const newUser = await usersRepository.inviteUser(input.email) + const invitedUser = await usersRepository.inviteUser(email) - if (!newUser) { + if (!invitedUser) { throw new AuthenticationError("User not invited") } - return { - ...newUser - } + return invitedUser } ) } diff --git a/sigap-website/src/application/use-cases/users/unban-user.use-case.ts b/sigap-website/src/application/use-cases/users/unban-user.use-case.ts new file mode 100644 index 0000000..66d2028 --- /dev/null +++ b/sigap-website/src/application/use-cases/users/unban-user.use-case.ts @@ -0,0 +1,27 @@ +import { NotFoundError } from "@/src/entities/errors/common" +import { IUsersRepository } from "../../repositories/users.repository.interface" +import { IInstrumentationService } from "../../services/instrumentation.service.interface" +import { User } from "@/src/entities/models/users/users.model" + +export type IUnbanUserUseCase = ReturnType + +export function unbanUserUseCase( + instrumentationService: IInstrumentationService, + usersRepository: IUsersRepository +) { + return async (id: string): Promise => { + return await instrumentationService.startSpan({ name: "unbanUser Use Case", op: "function" }, + async () => { + const existingUser = await usersRepository.getUserById(id) + + if (!existingUser) { + throw new NotFoundError("User not found") + } + + const unbanUser = await usersRepository.unbanUser(id) + + return unbanUser + } + ) + } +} \ No newline at end of file diff --git a/sigap-website/src/application/use-cases/users/update-user.use-case.ts b/sigap-website/src/application/use-cases/users/update-user.use-case.ts index 3d41dda..8b084eb 100644 --- a/sigap-website/src/application/use-cases/users/update-user.use-case.ts +++ b/sigap-website/src/application/use-cases/users/update-user.use-case.ts @@ -8,7 +8,7 @@ export type IUpdateUserUseCase = ReturnType const updateUserUseCase = ( instrumentationService: IInstrumentationService, usersRepository: IUsersRepository -) => async (id: string, input: UpdateUser): Promise => { +) => async (id: string, input: UpdateUser): Promise => { return await instrumentationService.startSpan({ name: "updateUser Use Case", op: "function" }, async () => { @@ -20,9 +20,7 @@ const updateUserUseCase = ( const updatedUser = await usersRepository.updateUser(id, input) - return { - ...updatedUser - } + return updatedUser } ) } \ No newline at end of file diff --git a/sigap-website/src/infrastructure/repositories/users.repository.impl.ts b/sigap-website/src/infrastructure/repositories/users.repository.ts similarity index 84% rename from sigap-website/src/infrastructure/repositories/users.repository.impl.ts rename to sigap-website/src/infrastructure/repositories/users.repository.ts index c489df8..4fff1ae 100644 --- a/sigap-website/src/infrastructure/repositories/users.repository.impl.ts +++ b/sigap-website/src/infrastructure/repositories/users.repository.ts @@ -6,7 +6,7 @@ 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 { DatabaseOperationError, NotFoundError } from "@/src/entities/errors/common"; import { AuthenticationError } from "@/src/entities/errors/auth"; export class UsersRepository implements IUsersRepository { @@ -39,6 +39,10 @@ export class UsersRepository implements IUsersRepository { } ) + if (!users) { + throw new NotFoundError("Users not found"); + } + return users; } catch (err) { this.crashReporterService.report(err); @@ -71,8 +75,14 @@ export class UsersRepository implements IUsersRepository { } ) - if (user) - return user; + if (!user) { + throw new NotFoundError("User not found"); + } + + return { + ...user, + id, + }; } catch (err) { this.crashReporterService.report(err); throw err; @@ -106,9 +116,13 @@ export class UsersRepository implements IUsersRepository { } ) - if (user) - return user; + + if (!user) { + throw new NotFoundError("User not found"); + } + + return user; } catch (err) { this.crashReporterService.report(err); throw err; @@ -141,34 +155,9 @@ export class UsersRepository implements IUsersRepository { } ) - if (user) - return user; - - } catch (err) { - this.crashReporterService.report(err); - throw err; - } - }) - } - - async getCurrentUser(): Promise { - 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; - } - ) + if (!user) { + throw new NotFoundError("User not found"); + } return user; } catch (err) { @@ -178,18 +167,52 @@ export class UsersRepository implements IUsersRepository { }) } - async createUser(input: CreateUser, tx?: ITransaction): Promise { + async getCurrentUser(): Promise { + return await this.instrumentationService.startSpan({ + name: "UsersRepository > getCurrentUser", + }, async () => { + try { + const supabase = await this.supabaseServer; + + const query = supabase.auth.getUser(); + + const { data, error } = await this.instrumentationService.startSpan({ + name: "UsersRepository > getCurrentUser > supabase.auth.getUser", + op: "db:query", + attributes: { "system": "supabase.auth" }, + }, + async () => { + return await query; + } + ) + + if (error) { + throw new AuthenticationError("Failed to get current user"); + } + + if (!data) { + throw new NotFoundError("User not found"); + } + + return { + ...data, + id: data.user.id, + }; + } catch (err) { + this.crashReporterService.report(err); + throw err; + } + }) + } + + async createUser(input: CreateUser, tx?: ITransaction): Promise { 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 query = supabase.auth.admin.createUser(input) const { data: { user } } = await this.instrumentationService.startSpan({ name: "UsersRepository > createUser > supabase.auth.admin.createUser", @@ -201,6 +224,10 @@ export class UsersRepository implements IUsersRepository { } ) + if (!user) { + throw new DatabaseOperationError("Failed to create user"); + } + return user; } catch (err) { @@ -210,7 +237,7 @@ export class UsersRepository implements IUsersRepository { }) } - async inviteUser(email: string, tx?: ITransaction): Promise { + async inviteUser(email: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > inviteUser", }, async () => { @@ -229,6 +256,10 @@ export class UsersRepository implements IUsersRepository { } ) + if (!user) { + throw new DatabaseOperationError("Failed to invite user"); + } + return user; } catch (err) { this.crashReporterService.report(err); @@ -237,7 +268,7 @@ export class UsersRepository implements IUsersRepository { }) } - async updateUser(id: string, input: Partial, tx?: ITransaction): Promise { + async updateUser(id: string, input: Partial, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > updateUser", }, async () => { @@ -267,7 +298,7 @@ export class UsersRepository implements IUsersRepository { ) if (error) { - throw new AuthenticationError(error.message); + throw new DatabaseOperationError("Failed to update user"); } const queryGetUser = db.users.findUnique({ @@ -332,18 +363,13 @@ export class UsersRepository implements IUsersRepository { } ) + if (!updatedUser) { + throw new DatabaseOperationError("Failed to update user"); + } + return { - data: { - user: { - ...data.user, - role: updatedUser.role, - profile: { - user_id: id, - ...updatedUser.profile, - }, - }, - }, - error: null, + ...updatedUser, + id, }; } catch (err) { @@ -353,7 +379,7 @@ export class UsersRepository implements IUsersRepository { }) } - async deleteUser(id: string, tx?: ITransaction): Promise { + async deleteUser(id: string, tx?: ITransaction): Promise { return await this.instrumentationService.startSpan({ name: "UsersRepository > deleteUser", }, async () => { @@ -362,7 +388,7 @@ export class UsersRepository implements IUsersRepository { const query = supabase.auth.admin.deleteUser(id); - await this.instrumentationService.startSpan({ + const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > deleteUser > supabase.auth.admin.deleteUser", op: "db:query", attributes: { "system": "supabase.auth" }, @@ -372,7 +398,14 @@ export class UsersRepository implements IUsersRepository { } ) - return; + if (error) { + throw new DatabaseOperationError("Failed to delete user"); + } + + return { + ...user, + id + }; } catch (err) { this.crashReporterService.report(err); @@ -392,7 +425,7 @@ export class UsersRepository implements IUsersRepository { ban_duration: ban_duration ?? "100h", }) - const { data, error } = await this.instrumentationService.startSpan({ + const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > banUser > supabase.auth.admin.updateUserById", op: "db:query", attributes: { "system": "supabase.auth" }, @@ -403,10 +436,13 @@ export class UsersRepository implements IUsersRepository { ) if (error) { - throw new AuthenticationError(error.message); + throw new DatabaseOperationError("Failed to ban user"); } - return data.user; + return { + ...user, + id + }; } catch (err) { this.crashReporterService.report(err); @@ -427,7 +463,7 @@ export class UsersRepository implements IUsersRepository { ban_duration: "none", }) - const { data, error } = await this.instrumentationService.startSpan({ + const { data: user, error } = await this.instrumentationService.startSpan({ name: "UsersRepository > unbanUser > supabase.auth.admin.updateUserById", op: "db:query", attributes: { "system": "supabase.auth" }, @@ -438,10 +474,13 @@ export class UsersRepository implements IUsersRepository { ) if (error) { - throw new AuthenticationError(error.message); + throw new DatabaseOperationError("Failed to unban user"); } - return data.user; + return { + ...user, + id + }; } catch (err) { this.crashReporterService.report(err); diff --git a/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.tsx b/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.tsx index 930ee09..39610b9 100644 --- a/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.tsx +++ b/sigap-website/src/interface-adapters/controllers/auth/sign-in.controller.tsx @@ -1,201 +1,8 @@ -// "use client"; - -import { useRouter } from "next/navigation"; -import { - defaultSignInPasswordlessValues, - SignInFormData, - SignInSchema, -} from "@/src/entities/models/auth/sign-in.model"; -import { useState, type FormEvent, type ChangeEvent } from "react"; -import { toast } from "sonner"; import { z } from "zod"; -// import { signIn } from ""; -import { useAuthActions } from "./auth-controller"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { AuthenticationError } from "@/src/entities/errors/auth"; import { ISignInUseCase } from "@/src/application/use-cases/auth/sign-in.use-case"; import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; import { InputParseError } from "@/src/entities/errors/common"; -type SignInFormErrors = Partial>; - -// export function useSignInForm() { -// const [formData, setFormData] = useState(defaultSignInValues); -// const [errors, setErrors] = useState({}); -// const [isSubmitting, setIsSubmitting] = useState(false); -// const [message, setMessage] = useState(null); -// const router = useRouter(); - -// const validateForm = (): boolean => { -// try { -// SignInSchema.parse(formData); -// setErrors({}); -// return true; -// } catch (error) { -// if (error instanceof z.ZodError) { -// const formattedErrors: SignInFormErrors = {}; -// error.errors.forEach((err) => { -// const path = err.path[0] as keyof SignInFormData; -// formattedErrors[path] = err.message; -// }); -// setErrors(formattedErrors); -// } -// return false; -// } -// }; - -// const handleChange = (e: ChangeEvent) => { -// const { name, value } = e.target; -// setFormData((prev) => ({ -// ...prev, -// [name]: value, -// })); -// }; - -// const handleSubmit = async (e: FormEvent) => { -// e.preventDefault(); -// if (!validateForm()) { -// return; -// } - -// setIsSubmitting(true); -// setMessage(null); - -// try { -// const result = await signIn(formData); - -// if (result.success) { -// setMessage(result.message); -// toast.success(result.message); - -// // Handle client-side navigation -// if (result.redirectTo) { -// router.push(result.redirectTo); -// } -// } else { -// setErrors({ -// email: result.message || "Sign in failed. Please try again.", -// }); -// toast.error(result.message || "Sign in failed. Please try again."); -// } -// } catch (error) { -// console.error("Sign in failed", error); -// setErrors({ -// email: "An unexpected error occurred. Please try again.", -// }); -// toast.error("An unexpected error occurred. Please try again."); -// } finally { -// setIsSubmitting(false); -// } -// }; - -// return { -// formData, -// errors, -// isSubmitting, -// message, -// setFormData, -// handleChange, -// handleSubmit, -// }; -// } - -// export function useSignInController() { -// const [formData, setFormData] = useState(defaultSignInValues); -// const [errors, setErrors] = useState>({}); - -// const { signIn } = useAuthActions(); - -// const form = useForm({ -// resolver: zodResolver(SignInSchema), -// defaultValues: defaultSignInValues, -// }); - -// // Handle input changes -// const handleChange = (e: React.ChangeEvent) => { -// const { name, value } = e.target; -// setFormData(prev => ({ -// ...prev, -// [name]: value -// })); - -// // Clear error when user starts typing -// if (errors[name]) { -// setErrors(prev => ({ -// ...prev, -// [name]: '' -// })); -// } -// }; - -// // Direct handleSubmit handler for the form -// const handleSubmit = async (e: React.FormEvent) => { -// e.preventDefault(); -// setErrors({}); - -// try { -// // Basic email validation before sending to API -// if (!formData.email || !formData.email.includes('@')) { -// setErrors({ email: 'Please enter a valid email address' }); -// return; -// } - -// await signIn.mutate(formData); -// } catch (error) { -// // This catch block will likely not be used since errors are handled in the mutation -// console.error("Form submission error:", error); -// } -// }; - -// // Combine form validation errors with API errors -// const formErrors = { -// ...errors, -// // If there's an API error from the mutation, add it to the appropriate field -// ...(signIn.error instanceof AuthenticationError ? -// { email: signIn.error.message } : -// {}) -// }; - -// return { -// formData, -// handleChange, -// handleSubmit, -// isPending: signIn.isPending, -// error: formErrors -// }; -// } - -// export function useSignInController() { -// const { signIn } = useAuthActions(); - -// // Gunakan react-hook-form untuk mengelola form state & error handling -// const { -// register, -// handleSubmit, -// formState: { errors }, -// } = useForm({ -// resolver: zodResolver(SignInSchema), -// defaultValues: defaultSignInPasswordlessValues, -// }); - -// // Handler untuk submit form -// const onSubmit = handleSubmit(async (data) => { -// try { -// signIn.mutate(data); -// } catch (error) { -// console.error("Sign-in submission error:", error); -// } -// }); - -// return { -// register, -// handleSubmit: onSubmit, -// errors, -// isPending: signIn.isPending, -// }; -// } - // Sign In Controller const signInInputSchema = z.object({ email: z.string().email("Please enter a valid email address"), diff --git a/sigap-website/src/interface-adapters/controllers/auth/verify-otp.controller.tsx b/sigap-website/src/interface-adapters/controllers/auth/verify-otp.controller.tsx index 462bdb4..902c56a 100644 --- a/sigap-website/src/interface-adapters/controllers/auth/verify-otp.controller.tsx +++ b/sigap-website/src/interface-adapters/controllers/auth/verify-otp.controller.tsx @@ -3,105 +3,6 @@ import { IVerifyOtpUseCase } from "@/src/application/use-cases/auth/verify-otp.u import { z } from "zod"; import { InputParseError } from "@/src/entities/errors/common"; -// export function useVerifyOtpForm(email: string) { -// const [isSubmitting, setIsSubmitting] = useState(false); -// const [message, setMessage] = useState(null); -// const { router } = useNavigations(); - -// const form = useForm({ -// resolver: zodResolver(verifyOtpSchema), -// defaultValues: { ...defaultVerifyOtpValues, email: email }, -// }); - -// const onSubmit = async (data: VerifyOtpFormData) => { -// setIsSubmitting(true); -// setMessage(null); - -// try { -// const result = await verifyOtp(data); - -// if (result.success) { -// setMessage(result.message); -// // Redirect or update UI state as needed -// toast.success(result.message); -// if (result.redirectTo) { -// router.push(result.redirectTo); -// } -// } else { -// toast.error(result.message); -// form.setError("token", { type: "manual", message: result.message }); -// } -// } catch (error) { -// console.error("OTP verification failed", error); -// toast.error("An unexpected error occurred. Please try again."); -// form.setError("token", { -// type: "manual", -// message: "An unexpected error occurred. Please try again.", -// }); -// } finally { -// setIsSubmitting(false); -// } -// }; - -// return { -// form, -// isSubmitting, -// message, -// onSubmit, -// }; -// } - -// export const useVerifyOtpController = (email: string) => { -// const { verifyOtp } = useAuthActions() - -// const { -// control, -// register, -// handleSubmit, -// reset, -// formState: { errors, isSubmitSuccessful }, -// } = useForm({ -// resolver: zodResolver(verifyOtpSchema), -// defaultValues: { ...defaultVerifyOtpValues, email: email }, -// }) - -// // Clear form after successful submission -// useEffect(() => { -// if (isSubmitSuccessful) { -// reset({ ...defaultVerifyOtpValues, email }) -// } -// }, [isSubmitSuccessful, reset, email]) - -// const onSubmit = handleSubmit(async (data) => { -// try { -// await verifyOtp.mutate(data) -// } catch (error) { -// console.error("OTP verification failed", error) -// } -// }) - -// // Function to handle auto-submission when all digits are entered -// const handleOtpChange = (value: string, onChange: (value: string) => void) => { -// onChange(value) - -// // Auto-submit when all 6 digits are entered -// if (value.length === 6) { -// setTimeout(() => { -// onSubmit() -// }, 300) // Small delay to allow the UI to update -// } -// } - -// return { -// control, -// register, -// handleSubmit: onSubmit, -// handleOtpChange, -// errors, -// isPending: verifyOtp.isPending, -// } -// } - // Verify OTP Controller const verifyOtpInputSchema = z.object({ email: z.string().email("Please enter a valid email address"), diff --git a/sigap-website/src/interface-adapters/controllers/users/ban-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/ban-user.controller.ts new file mode 100644 index 0000000..6cca05e --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/ban-user.controller.ts @@ -0,0 +1,27 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IBanUserUseCase } from "@/src/application/use-cases/users/ban-user.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; +import { z } from "zod"; + +const inputSchema = z.object({ + id: z.string(), + ban_duration: z.string() +}) + +export type IBanUserController = ReturnType + +export const banUserController = ( + instrumentationService: IInstrumentationService, + banUserUseCase: IBanUserUseCase +) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "banUser Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await banUserUseCase(data.id, data.ban_duration); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/create-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/create-user.controller.ts new file mode 100644 index 0000000..d20cd33 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/create-user.controller.ts @@ -0,0 +1,33 @@ +import { IUsersRepository } from "@/src/application/repositories/users.repository.interface" +import { IAuthenticationService } from "@/src/application/services/authentication.service.interface" +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface" +import { ICreateUserUseCase } from "@/src/application/use-cases/users/create-user.use-case" +import { UnauthenticatedError } from "@/src/entities/errors/auth" +import { InputParseError } from "@/src/entities/errors/common" +import { CreateUserSchema } from "@/src/entities/models/users/users.model" +import { z } from "zod" + +const inputSchema = CreateUserSchema + +export type ICreateUserController = ReturnType + +export const createUserController = ( + instrumentationService: IInstrumentationService, + createUserUseCase: ICreateUserUseCase, + authenticationService: IAuthenticationService +) => async (input: Partial>) => { + + const session = await authenticationService.getSession() + + if (!session) { + throw new UnauthenticatedError("Must be logged in to create a todo") + } + + const { data, error: inputParseError } = inputSchema.safeParse(input) + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }) + } + + return await createUserUseCase(data); +} \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/delete-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/delete-user.controller.ts new file mode 100644 index 0000000..c3db458 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/delete-user.controller.ts @@ -0,0 +1,25 @@ +import { IAuthenticationService } from "@/src/application/services/authentication.service.interface" +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface" +import { IDeleteUserUseCase } from "@/src/application/use-cases/users/delete-user.use-case" +import { UnauthenticatedError } from "@/src/entities/errors/auth" + +export type IDeleteUserController = ReturnType + +export const deleteUserController = + ( + instrumentationService: IInstrumentationService, + deleteUserUseCase: IDeleteUserUseCase, + authenticationService: IAuthenticationService + ) => + async (id: string) => { + return await instrumentationService.startSpan({ name: "deleteUser Controller" }, async () => { + + const session = await authenticationService.getSession() + + if (!session) { + throw new UnauthenticatedError("Must be logged in to create a todo") + } + + return await deleteUserUseCase(id); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/get-current-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/get-current-user.controller.ts new file mode 100644 index 0000000..ecade63 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/get-current-user.controller.ts @@ -0,0 +1,18 @@ +import { IUsersRepository } from "@/src/application/repositories/users.repository.interface" +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface" +import { IGetCurrentUserUseCase } from "@/src/application/use-cases/users/get-current-user.use-case" +import { NotFoundError } from "@/src/entities/errors/common" + +export type IGetCurrentUserController = ReturnType + +export const getCurrentUserController = + ( + instrumentationService: IInstrumentationService, + getCurrentUserUseCase: IGetCurrentUserUseCase + ) => + async () => { + return await instrumentationService.startSpan({ name: "getCurrentUser Controller" }, async () => { + + return await getCurrentUserUseCase(); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/get-list-users.controller.ts b/sigap-website/src/interface-adapters/controllers/users/get-list-users.controller.ts new file mode 100644 index 0000000..848609a --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/get-list-users.controller.ts @@ -0,0 +1,15 @@ +import { IUsersRepository } from "@/src/application/repositories/users.repository.interface" +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface" + +export type IGetListUserController = ReturnType + +export const getListUsersController = + ( + instrumentationService: IInstrumentationService, + usersRepository: IUsersRepository + ) => + async () => { + return await instrumentationService.startSpan({ name: "getListUsers Controller" }, async () => { + return await usersRepository.listUsers(); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/get-user-by-email.controller.ts b/sigap-website/src/interface-adapters/controllers/users/get-user-by-email.controller.ts new file mode 100644 index 0000000..14f827d --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/get-user-by-email.controller.ts @@ -0,0 +1,27 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IGetUserByEmailUseCase } from "@/src/application/use-cases/users/get-user-by-email.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; +import { z } from "zod"; + +const inputSchema = z.object({ + email: z.string().email() +}) + +export type IGetUserByEmailController = ReturnType + +export const getUserByEmailController = + ( + instrumentationService: IInstrumentationService, + getUserByEmailUseCase: IGetUserByEmailUseCase + ) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "getUserByEmail Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await getUserByEmailUseCase(data.email); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/get-user-by-id.controller.ts b/sigap-website/src/interface-adapters/controllers/users/get-user-by-id.controller.ts new file mode 100644 index 0000000..1f5ef5d --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/get-user-by-id.controller.ts @@ -0,0 +1,26 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface" +import { IGetUserByIdUseCase } from "@/src/application/use-cases/users/get-user-by-id.use-case" +import { InputParseError } from "@/src/entities/errors/common" +import { z } from "zod" + +const inputSchema = z.object({ + id: z.string() +}) + +export type IGetUserByIdController = ReturnType + +export const getUserByIdController = ( + instrumentationService: IInstrumentationService, + getUserByIdUseCase: IGetUserByIdUseCase +) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "getUserById Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await getUserByIdUseCase(data.id); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/get-user-by-username.controller.ts b/sigap-website/src/interface-adapters/controllers/users/get-user-by-username.controller.ts new file mode 100644 index 0000000..beaa776 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/get-user-by-username.controller.ts @@ -0,0 +1,27 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IGetUserByUsernameUseCase } from "@/src/application/use-cases/users/get-user-by-username.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; +import { z } from "zod"; + +const inputSchema = z.object({ + username: z.string() +}) + +export type IGetUserByUsernameController = ReturnType + +export const getUserByUsernameController = + ( + instrumentationService: IInstrumentationService, + getUserByUsernameUseCase: IGetUserByUsernameUseCase + ) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "getUserByUsername Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await getUserByUsernameUseCase(data.username); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/invite-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/invite-user.controller.ts new file mode 100644 index 0000000..f8d5572 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/invite-user.controller.ts @@ -0,0 +1,28 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IInviteUserUseCase } from "@/src/application/use-cases/users/invite-user.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; +import { z } from "zod"; + +const inputSchema = z.object({ + email: z.string().email(), +}) + +export type IInviteUserController = ReturnType + +export const inviteUserController = + ( + instrumentationService: IInstrumentationService, + inviteUserUseCase: IInviteUserUseCase + ) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "inviteUser Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await inviteUserUseCase(data.email); + }) + } + diff --git a/sigap-website/src/interface-adapters/controllers/users/unban-user.controller.ts b/sigap-website/src/interface-adapters/controllers/users/unban-user.controller.ts new file mode 100644 index 0000000..4e119a7 --- /dev/null +++ b/sigap-website/src/interface-adapters/controllers/users/unban-user.controller.ts @@ -0,0 +1,26 @@ +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IUnbanUserUseCase } from "@/src/application/use-cases/users/unban-user.use-case"; +import { InputParseError } from "@/src/entities/errors/common"; +import { z } from "zod"; + +const inputSchema = z.object({ + id: z.string() +}) + +export type IUnbanUserController = ReturnType + +export const unbanUserController = ( + instrumentationService: IInstrumentationService, + unbanUserUseCase: IUnbanUserUseCase +) => + async (input: Partial>) => { + return await instrumentationService.startSpan({ name: "unbanUser Controller" }, async () => { + const { data, error: inputParseError } = inputSchema.safeParse(input); + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }); + } + + return await unbanUserUseCase(data.id); + }) + } \ No newline at end of file diff --git a/sigap-website/src/interface-adapters/controllers/users/update-user-controller.ts b/sigap-website/src/interface-adapters/controllers/users/update-user-controller.ts index e69de29..82e8367 100644 --- a/sigap-website/src/interface-adapters/controllers/users/update-user-controller.ts +++ b/sigap-website/src/interface-adapters/controllers/users/update-user-controller.ts @@ -0,0 +1,36 @@ +import { IAuthenticationService } from "@/src/application/services/authentication.service.interface"; +import { IInstrumentationService } from "@/src/application/services/instrumentation.service.interface"; +import { IUpdateUserUseCase } from "@/src/application/use-cases/users/update-user.use-case"; +import { UnauthenticatedError } from "@/src/entities/errors/auth"; +import { InputParseError } from "@/src/entities/errors/common"; +import { UpdateUser, UpdateUserSchema } from "@/src/entities/models/users/users.model"; +import { z } from "zod"; + +const inputSchema = UpdateUserSchema + +export type IUpdateUserController = ReturnType + +export const updateUserController = + ( + instrumentationService: IInstrumentationService, + updateUserUseCase: IUpdateUserUseCase, + authenticationService: IAuthenticationService + ) => + async (id: string, input: Partial>,) => { + return await instrumentationService.startSpan({ name: "updateUser Controller" }, async () => { + + const session = await authenticationService.getSession() + + if (!session) { + throw new UnauthenticatedError("Must be logged in to create a todo") + } + + const { data, error: inputParseError } = inputSchema.safeParse(input) + + if (inputParseError) { + throw new InputParseError("Invalid data", { cause: inputParseError }) + } + + return await updateUserUseCase(id, data); + }) + } \ No newline at end of file diff --git a/sigap-website/tailwind.config.ts b/sigap-website/tailwind.config.ts index 41668a3..115fadb 100644 --- a/sigap-website/tailwind.config.ts +++ b/sigap-website/tailwind.config.ts @@ -52,6 +52,16 @@ const config = { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, + sidebar: { + DEFAULT: "hsl(var(--sidebar-background))", + foreground: "hsl(var(--sidebar-foreground))", + primary: "hsl(var(--sidebar-primary))", + "primary-foreground": "hsl(var(--sidebar-primary-foreground))", + accent: "hsl(var(--sidebar-accent))", + "accent-foreground": "hsl(var(--sidebar-accent-foreground))", + border: "hsl(var(--sidebar-border))", + ring: "hsl(var(--sidebar-ring))", + }, }, borderRadius: { lg: "var(--radius)",