diff --git a/package.json b/package.json index 2964c5e20..1c6684ced 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "react-hook-form": "^7.46.2", "react-redux": "^8.1.2", "redux-persist": "^6.0.0", + "reflect-metadata": "^0.2.2", "tailwind-merge": "^2.0.0", + "tsyringe": "^4.8.0", "zod": "^3.22.2" }, "devDependencies": { diff --git a/src/app/(auth)/authService.ts b/src/app/(auth)/authService.ts index 2a8c54999..24df5341b 100644 --- a/src/app/(auth)/authService.ts +++ b/src/app/(auth)/authService.ts @@ -1,7 +1,8 @@ "use server"; import { cookies } from "next/headers"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { getAccessToken, getRefreshToken } from "@/utils/getCookie"; import { POST, UNAUTHPOST } from "@/utils/requests"; diff --git a/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts b/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts index ef0a600a7..36ef73887 100644 --- a/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts +++ b/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts @@ -2,7 +2,8 @@ import { fetchSprints } from "@/myVoyage/sprints/components/RedirectToCurrentSpr import { fetchMeeting } from "@/myVoyage/sprints/components/SprintWrapper"; import type { User } from "@/store/features/user/userSlice"; import type { Sprint, Voyage } from "@/store/features/sprint/sprintSlice"; -import type { AppError } from "@/types/types"; +import { type AppError } from "@/types/types"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { getCurrentSprint } from "@/utils/getCurrentSprint"; import { getCurrentVoyageData } from "@/utils/getCurrentVoyageData"; import { fetchResources } from "@/app/(main)/my-voyage/[teamId]/voyage-resources/components/ResourcesComponentWrapper"; @@ -13,7 +14,6 @@ import { type FeaturesList } from "@/store/features/features/featuresSlice"; import { type IdeationData } from "@/store/features/ideation/ideationSlice"; import { type TechStackData } from "@/store/features/techStack/techStackSlice"; import { type ResourceData } from "@/store/features/resources/resourcesSlice"; -import type { AsyncActionResponse } from "@/utils/handleAsync"; import { ErrorType } from "@/utils/error"; interface GetDashboardDataResponse { diff --git a/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx index cc3780ebf..53bf306d5 100644 --- a/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx @@ -9,7 +9,8 @@ import ErrorComponent from "@/components/Error"; import { type TeamDirectory } from "@/store/features/directory/directorySlice"; import { getAccessToken } from "@/utils/getCookie"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { GET } from "@/utils/requests"; import { CacheTag } from "@/utils/cacheTag"; import { type User } from "@/store/features/user/userSlice"; diff --git a/src/app/(main)/my-voyage/[teamId]/directory/directoryService.ts b/src/app/(main)/my-voyage/[teamId]/directory/directoryService.ts index 299774e73..eaf9dc43d 100644 --- a/src/app/(main)/my-voyage/[teamId]/directory/directoryService.ts +++ b/src/app/(main)/my-voyage/[teamId]/directory/directoryService.ts @@ -2,7 +2,8 @@ import { revalidateTag } from "next/cache"; import { getAccessToken } from "@/utils/getCookie"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { PATCH } from "@/utils/requests"; import { CacheTag } from "@/utils/cacheTag"; diff --git a/src/app/(main)/my-voyage/[teamId]/features/components/FeaturesComponentWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/features/components/FeaturesComponentWrapper.tsx index 008df74e6..ca81309b8 100644 --- a/src/app/(main)/my-voyage/[teamId]/features/components/FeaturesComponentWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/features/components/FeaturesComponentWrapper.tsx @@ -16,7 +16,8 @@ import { getUser } from "@/utils/getUser"; import { getAccessToken } from "@/utils/getCookie"; import { GET } from "@/utils/requests"; import { CacheTag } from "@/utils/cacheTag"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { ErrorType } from "@/utils/error"; function transformData(features: Features[]): FeaturesList[] { diff --git a/src/app/(main)/my-voyage/[teamId]/features/featuresService.ts b/src/app/(main)/my-voyage/[teamId]/features/featuresService.ts index 318be5f40..3597c1b47 100644 --- a/src/app/(main)/my-voyage/[teamId]/features/featuresService.ts +++ b/src/app/(main)/my-voyage/[teamId]/features/featuresService.ts @@ -4,7 +4,8 @@ import { revalidateTag } from "next/cache"; import { type Features } from "@/store/features/features/featuresSlice"; import { CacheTag } from "@/utils/cacheTag"; import { getAccessToken } from "@/utils/getCookie"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { DELETE, PATCH, POST } from "@/utils/requests"; interface SaveOrderProps { diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationClientAdapter.ts b/src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationClientAdapter.ts new file mode 100644 index 000000000..256b95167 --- /dev/null +++ b/src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationClientAdapter.ts @@ -0,0 +1,34 @@ +import { type IdeationClientPort } from "@/modules/ideation/ports/primary/ideationClientPort"; +import { type AddIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; +import { AddIdeationUseCase } from "@/modules/ideation/application/usecases/addIdeationUseCase"; +import { NextJsRestApiAdapter } from "@/modules/restApi/adapters/secondary/nextJsRestApiAdapter"; +import { IdeationApiAdapter } from "@/modules/ideation/adapters/secondary/ideationApiAdapter"; +import { type AddIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; + +const nextJsRestApiAdapter = new NextJsRestApiAdapter( + process.env.NEXT_PUBLIC_API_URL!, +); +const ideationApiPort = new IdeationApiAdapter(nextJsRestApiAdapter); + +export class IdeationClientAdapter implements IdeationClientPort { + async addIdeation({ + teamId, + title, + description, + vision, + token, + cache, + }: Required): Promise { + const addIdeationUseCase = new AddIdeationUseCase(ideationApiPort); + + // refactor this later to not expect a function + return await addIdeationUseCase.execute({ + teamId, + title, + description, + vision, + cache, + token, + }); + } +} diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/ideationService.ts b/src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA.ts similarity index 86% rename from src/app/(main)/my-voyage/[teamId]/ideation/ideationService.ts rename to src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA.ts index fa2ae7590..36e357d6d 100644 --- a/src/app/(main)/my-voyage/[teamId]/ideation/ideationService.ts +++ b/src/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA.ts @@ -1,10 +1,14 @@ "use server"; import { revalidateTag } from "next/cache"; +import { IdeationClientAdapter } from "./ideationClientAdapter"; import { getAccessToken } from "@/utils/getCookie"; import { DELETE, PATCH, POST } from "@/utils/requests"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { CacheTag } from "@/utils/cacheTag"; +import { type AddIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; +import { type AddIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; interface IdeationProps { teamId: number; @@ -53,21 +57,27 @@ export interface IdeationVoteResponse extends IdeationResponse { projectIdeaId: number; } +const addIdeationClientAdapter = new IdeationClientAdapter(); + export async function addIdeation({ teamId, title, description, vision, -}: AddIdeationProps): Promise> { +}: AddIdeationRequestDto): Promise< + AsyncActionResponse +> { const token = getAccessToken(); const addIdeationAsync = () => - POST( - `api/v1/voyages/teams/${teamId}/ideations`, + addIdeationClientAdapter.addIdeation({ + teamId, + title, + description, + vision, token, - "default", - { title, description, vision }, - ); + cache: "default", + }); const [res, error] = await handleAsync(addIdeationAsync); diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationComponentWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationComponentWrapper.tsx index 29d74891d..277dd5b65 100644 --- a/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationComponentWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationComponentWrapper.tsx @@ -5,7 +5,7 @@ import FinalizedIdeationCard from "./FinalizedIdeationCard"; import IdeationContainer from "./IdeationContainer"; import IdeationProvider from "./IdeationProvider"; import VoteCard from "./VoteCard"; -import { type FetchIdeationsProps } from "@/app/(main)/my-voyage/[teamId]/ideation/ideationService"; +import { type FetchIdeationsProps } from "@/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA"; import Banner from "@/components/banner/Banner"; import VoyagePageBannerContainer from "@/components/banner/VoyagePageBannerContainer"; import ErrorComponent from "@/components/Error"; @@ -15,7 +15,8 @@ import { CacheTag } from "@/utils/cacheTag"; import { getAccessToken } from "@/utils/getCookie"; import { getCurrentVoyageData } from "@/utils/getCurrentVoyageData"; import { getUser } from "@/utils/getUser"; -import { handleAsync, type AsyncActionResponse } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { GET } from "@/utils/requests"; import routePaths from "@/utils/routePaths"; import { ErrorType } from "@/utils/error"; diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationForm.tsx b/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationForm.tsx index 12e46f4cd..019df3ffb 100644 --- a/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationForm.tsx +++ b/src/app/(main)/my-voyage/[teamId]/ideation/components/IdeationForm.tsx @@ -18,7 +18,7 @@ import { type EditIdeationProps, addIdeation, deleteIdeation, -} from "@/app/(main)/my-voyage/[teamId]/ideation/ideationService"; +} from "@/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA"; import useServerAction from "@/hooks/useServerAction"; import { persistor } from "@/store/store"; import { onOpenModal } from "@/store/features/modal/modalSlice"; diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/components/VoteCard.tsx b/src/app/(main)/my-voyage/[teamId]/ideation/components/VoteCard.tsx index 042aa0ffb..4f2a5046e 100644 --- a/src/app/(main)/my-voyage/[teamId]/ideation/components/VoteCard.tsx +++ b/src/app/(main)/my-voyage/[teamId]/ideation/components/VoteCard.tsx @@ -13,7 +13,7 @@ import useServerAction from "@/hooks/useServerAction"; import { addIdeationVote, removeIdeationVote, -} from "@/app/(main)/my-voyage/[teamId]/ideation/ideationService"; +} from "@/app/(main)/my-voyage/[teamId]/ideation/adapters/ideationSA"; import { onOpenModal } from "@/store/features/modal/modalSlice"; import AvatarGroup from "@/components/avatar/AvatarGroup"; import Avatar from "@/components/avatar/Avatar"; diff --git a/src/app/(main)/my-voyage/[teamId]/ideation/finalize/components/ConfirmationButton.tsx b/src/app/(main)/my-voyage/[teamId]/ideation/finalize/components/ConfirmationButton.tsx index a8a59cd67..77931595b 100644 --- a/src/app/(main)/my-voyage/[teamId]/ideation/finalize/components/ConfirmationButton.tsx +++ b/src/app/(main)/my-voyage/[teamId]/ideation/finalize/components/ConfirmationButton.tsx @@ -2,7 +2,7 @@ import { useParams, useRouter } from "next/navigation"; import { type FinalizedIdeation } from "./FinalizeIdeationList"; import useServerAction from "@/hooks/useServerAction"; import Button from "@/components/Button"; -import { finalizeIdeation } from "@/myVoyage/ideation/ideationService"; +import { finalizeIdeation } from "@/myVoyage/ideation/adapters/ideationSA"; import routePaths from "@/utils/routePaths"; import { useAppDispatch } from "@/store/hooks"; import { onOpenModal } from "@/store/features/modal/modalSlice"; diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx index 67abcb457..82f199a9c 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx @@ -16,7 +16,8 @@ import { getUser } from "@/utils/getUser"; import { getCurrentSprint } from "@/utils/getCurrentSprint"; import { GET } from "@/utils/requests"; import { CacheTag } from "@/utils/cacheTag"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { type Sprint } from "@/store/features/sprint/sprintSlice"; import { getCurrentVoyageData } from "@/utils/getCurrentVoyageData"; import routePaths from "@/utils/routePaths"; diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx index b5edac318..053d718c2 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx @@ -25,7 +25,8 @@ import { } from "@/store/features/sprint/sprintSlice"; import { getCurrentSprint } from "@/utils/getCurrentSprint"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { GET } from "@/utils/requests"; import { getAccessToken } from "@/utils/getCookie"; import { getUser } from "@/utils/getUser"; diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx index ab645f770..62c56dd75 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx @@ -9,7 +9,8 @@ import { getAccessToken } from "@/utils/getCookie"; import { getUser } from "@/utils/getUser"; import { GET } from "@/utils/requests"; import { CacheTag } from "@/utils/cacheTag"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { getCurrentVoyageData } from "@/utils/getCurrentVoyageData"; import routePaths from "@/utils/routePaths"; import { Forms, UserRole } from "@/utils/form/formsEnums"; diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts b/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts index b42cd3dde..fb0b0ceec 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts +++ b/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts @@ -3,7 +3,8 @@ import { revalidateTag } from "next/cache"; import { getAccessToken } from "@/utils/getCookie"; import { DELETE, PATCH, POST } from "@/utils/requests"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { CacheTag } from "@/utils/cacheTag"; import { getSprintCache } from "@/utils/getSprintCache"; diff --git a/src/app/(main)/my-voyage/[teamId]/tech-stack/techStackService.ts b/src/app/(main)/my-voyage/[teamId]/tech-stack/techStackService.ts index f2362218a..9f8963d42 100644 --- a/src/app/(main)/my-voyage/[teamId]/tech-stack/techStackService.ts +++ b/src/app/(main)/my-voyage/[teamId]/tech-stack/techStackService.ts @@ -4,7 +4,8 @@ import { revalidateTag } from "next/cache"; import type { Category } from "./finalize/types"; import { CacheTag } from "@/utils/cacheTag"; import { getAccessToken } from "@/utils/getCookie"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { DELETE, PATCH, POST } from "@/utils/requests"; import { type TechStackItem } from "@/store/features/techStack/techStackSlice"; diff --git a/src/app/(main)/my-voyage/[teamId]/voyage-resources/resourcesService.ts b/src/app/(main)/my-voyage/[teamId]/voyage-resources/resourcesService.ts index 3c3fe3fda..6b7aa428b 100644 --- a/src/app/(main)/my-voyage/[teamId]/voyage-resources/resourcesService.ts +++ b/src/app/(main)/my-voyage/[teamId]/voyage-resources/resourcesService.ts @@ -3,7 +3,8 @@ import { revalidateTag } from "next/cache"; import { getAccessToken } from "@/utils/getCookie"; import { POST, DELETE } from "@/utils/requests"; -import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; +import { handleAsync } from "@/utils/handleAsync"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { CacheTag } from "@/utils/cacheTag"; interface ResourceProps { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2ac5ac26a..6cd832248 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,8 @@ import { Inter } from "next/font/google"; import StoreProvider from "@/components/providers/StoreProvider"; import ThemeProvider from "@/components/providers/ThemeProvider"; import ModalProvider from "@/components/providers/ModalProvider"; +import "reflect-metadata"; +import "@/di/config"; export const metadata: Metadata = { title: "Chingu Dashboard", diff --git a/src/di/config.ts b/src/di/config.ts new file mode 100644 index 000000000..7bc62de24 --- /dev/null +++ b/src/di/config.ts @@ -0,0 +1,17 @@ +import { container } from "tsyringe"; + +import { TYPES } from "./types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { IdeationApiAdapter } from "@/modules/ideation/adapters/secondary/ideationApiAdapter"; +import { type RestApiPort } from "@/modules/restApi/ports/secondary/restApiPort"; +import { NextJsRestApiAdapter } from "@/modules/restApi/adapters/secondary/nextJsRestApiAdapter"; + +container.register(TYPES.IdeationApiPort, { + useClass: IdeationApiAdapter, +}); + +container.register(TYPES.RestApiPort, { + useValue: new NextJsRestApiAdapter(process.env.NEXT_PUBLIC_API_URL!), +}); + +export default container; diff --git a/src/di/injectables.ts b/src/di/injectables.ts new file mode 100644 index 000000000..b65700683 --- /dev/null +++ b/src/di/injectables.ts @@ -0,0 +1,30 @@ +import { container } from "tsyringe"; +import { TYPES } from "./types"; +import type { AddIdeationUseCase } from "@/modules/ideation/application/usecases/addIdeationUseCase"; +import type { AddIdeationVoteUseCase } from "@/modules/ideation/application/usecases/addIdeationVoteUseCase"; +import type { DeleteIdeationUseCase } from "@/modules/ideation/application/usecases/deleteIdeationUseCase"; +import type { EditIdeationUseCase } from "@/modules/ideation/application/usecases/editIdeationUseCase"; +import type { FinalizeIdeationUseCase } from "@/modules/ideation/application/usecases/finalizeIdeationUseCase"; +import type { RemoveIdeationVoteUseCase } from "@/modules/ideation/application/usecases/removeIdeationVoteUseCase"; + +export const injectables = { + /* Ideation */ + addIdeationUseCase: container.resolve( + TYPES.AddIdeationUseCase, + ), + addIdeationVoteUseCase: container.resolve( + TYPES.AddIdeationVoteUseCase, + ), + deleteIdeationUseCase: container.resolve( + TYPES.DeleteIdeationUseCase, + ), + editIdeationUseCase: container.resolve( + TYPES.EditIdeationUseCase, + ), + finalizeIdeationUseCase: container.resolve( + TYPES.FinalizeIdeationUseCase, + ), + removeIdeationVoteUseCase: container.resolve( + TYPES.RemoveIdeationVoteUseCase, + ), +} as const; diff --git a/src/di/resolveInjection.ts b/src/di/resolveInjection.ts new file mode 100644 index 000000000..31627165d --- /dev/null +++ b/src/di/resolveInjection.ts @@ -0,0 +1,9 @@ +import "reflect-metadata"; + +import { injectables } from "./injectables"; + +const resolveInjection = () => ({ + ...injectables, +}); + +export default resolveInjection; diff --git a/src/di/resolver.ts b/src/di/resolver.ts new file mode 100644 index 000000000..f6daa89ac --- /dev/null +++ b/src/di/resolver.ts @@ -0,0 +1,10 @@ +import type { InjectionToken } from "tsyringe"; + +import container from "./config"; + +export const resolve = + (token: InjectionToken) => + // eslint-disable-next-line indent + () => + // eslint-disable-next-line indent + container.resolve(token); diff --git a/src/di/types.ts b/src/di/types.ts new file mode 100644 index 000000000..e2f538ec0 --- /dev/null +++ b/src/di/types.ts @@ -0,0 +1,18 @@ +export const TYPES = { + /* Ports */ + RestApiPort: Symbol.for("RestApiPort"), + IdeationApiPort: Symbol.for("IdeationApiPort"), + IdeationClientPort: Symbol.for("IdeationClientPort"), + + /* Adapters */ + // NextJsRestApiAdapter: Symbol.for("NextJsRestApiAdapter"), + IdeationApiAdapter: Symbol.for("IdeationApiAdapter"), + + /* UseCases */ + AddIdeationUseCase: Symbol.for("AddIdeationUseCase"), + AddIdeationVoteUseCase: Symbol.for("AddIdeationVoteUseCase"), + DeleteIdeationUseCase: Symbol.for("DeleteIdeationUseCase"), + EditIdeationUseCase: Symbol.for("EditIdeationUseCase"), + FinalizeIdeationUseCase: Symbol.for("FinalizeIdeationUseCase"), + RemoveIdeationVoteUseCase: Symbol.for("RemoveIdeationVoteUseCase"), +}; diff --git a/src/hooks/useInjection.ts b/src/hooks/useInjection.ts new file mode 100644 index 000000000..c23a03686 --- /dev/null +++ b/src/hooks/useInjection.ts @@ -0,0 +1,18 @@ +import "reflect-metadata"; + +import { useMemo } from "react"; + +import { injectables } from "@/di/injectables"; + +const useInjection = () => { + const injection = useMemo( + () => ({ + ...injectables, + }), + [], + ); + + return injection; +}; + +export default useInjection; diff --git a/src/modules/ideation/adapters/secondary/ideationApiAdapter.ts b/src/modules/ideation/adapters/secondary/ideationApiAdapter.ts new file mode 100644 index 000000000..2a31e7f93 --- /dev/null +++ b/src/modules/ideation/adapters/secondary/ideationApiAdapter.ts @@ -0,0 +1,128 @@ +import { inject, injectable } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { type RestApiPort } from "@/modules/restApi/ports/secondary/restApiPort"; +import type { + AddIdeationBodyDto, + AddIdeationRequestDto, + DeleteIdeationRequestDto, + EditIdeationBodyDto, + EditIdeationRequestDto, + FinalizeIdeationRequestDto, + IdeationVoteRequestDto, +} from "@/modules/ideation/application/dtos/request.dto"; +import type { + EditIdeationResponseDto, + AddIdeationResponseDto, + DeleteIdeationResponseDto, + IdeationVoteResponseDto, + FinalizeIdeationResponseDto, +} from "@/modules/ideation/application/dtos/response.dto"; +import { IdeationUrls } from "@/modules/ideation/application/constants/ideationUrls"; + +@injectable() +export class IdeationApiAdapter implements IdeationApiPort { + constructor( + @inject(TYPES.RestApiPort) + private readonly apiClient: RestApiPort, + ) {} + + async addIdeation({ + teamId, + title, + description, + vision, + cache, + token, + }: AddIdeationRequestDto): Promise { + return await this.apiClient.post< + AddIdeationBodyDto, + AddIdeationResponseDto + >({ + url: `${IdeationUrls.BASE_URL_TEAMS}/${teamId}/${IdeationUrls.SUB_URLS.IDEATIONS}`, + options: { + cache, + token, + }, + payload: { title, description, vision }, + }); + } + + async editIdeation({ + ideationId, + title, + description, + vision, + cache, + token, + }: EditIdeationRequestDto): Promise { + return await this.apiClient.patch< + EditIdeationBodyDto, + EditIdeationResponseDto + >({ + url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}`, + options: { + cache, + token, + }, + payload: { title, description, vision }, + }); + } + + async deleteIdeation({ + ideationId, + cache, + token, + }: DeleteIdeationRequestDto): Promise { + return await this.apiClient.delete({ + url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}`, + options: { + cache, + token, + }, + }); + } + + async addIdeationVote({ + ideationId, + cache, + token, + }: IdeationVoteRequestDto): Promise { + return await this.apiClient.post({ + url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.IDEATION_VOTES}`, + options: { + cache, + token, + }, + }); + } + + async removeIdeationVote({ + ideationId, + cache, + token, + }: IdeationVoteRequestDto): Promise { + return await this.apiClient.delete({ + url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.IDEATION_VOTES}`, + options: { + cache, + token, + }, + }); + } + + async finalizeIdeation({ + teamId, + ideationId, + cache, + token, + }: FinalizeIdeationRequestDto): Promise { + return await this.apiClient.post({ + url: `${IdeationUrls.BASE_URL_TEAMS}/${teamId}/${IdeationUrls.SUB_URLS.IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.SELECT}`, + options: { + cache, + token, + }, + }); + } +} diff --git a/src/modules/ideation/application/constants/ideationUrls.ts b/src/modules/ideation/application/constants/ideationUrls.ts new file mode 100644 index 000000000..134027dee --- /dev/null +++ b/src/modules/ideation/application/constants/ideationUrls.ts @@ -0,0 +1,9 @@ +export const IdeationUrls = { + BASE_URL_IDEATIONS: "api/v1/voyages/ideations", + BASE_URL_TEAMS: "api/v1/voyages/teams", + SUB_URLS: { + IDEATIONS: "ideations", + IDEATION_VOTES: "ideation-votes", + SELECT: "select", + }, +} as const; diff --git a/src/modules/ideation/application/dtos/common.dto.ts b/src/modules/ideation/application/dtos/common.dto.ts new file mode 100644 index 000000000..f6fbf7238 --- /dev/null +++ b/src/modules/ideation/application/dtos/common.dto.ts @@ -0,0 +1,11 @@ +import { type Ideation } from "@/modules/ideation/application/entities/ideation"; + +export type IdeationBodyDto = Pick< + Ideation, + "title" | "description" | "vision" +>; + +export interface IdeationRequestDto { + teamId: number; + ideationId: number; +} diff --git a/src/modules/ideation/application/dtos/request.dto.ts b/src/modules/ideation/application/dtos/request.dto.ts new file mode 100644 index 000000000..1d6045755 --- /dev/null +++ b/src/modules/ideation/application/dtos/request.dto.ts @@ -0,0 +1,35 @@ +import { type IdeationRequestDto, type IdeationBodyDto } from "./common.dto"; + +export type FetchIdeationsRequestDto = Pick; + +export interface AddIdeationBodyDto extends IdeationBodyDto {} + +export interface EditIdeationBodyDto extends Partial {} + +export interface AddIdeationRequestDto + extends Pick, + IdeationBodyDto { + cache?: RequestCache; + token?: string; +} + +export type EditIdeationRequestDto = EditIdeationBodyDto & + Omit & { + cache?: RequestCache; + token?: string; + }; + +export type DeleteIdeationRequestDto = Omit & { + cache?: RequestCache; + token?: string; +}; + +export type IdeationVoteRequestDto = Omit & { + cache?: RequestCache; + token?: string; +}; + +export interface FinalizeIdeationRequestDto extends IdeationRequestDto { + cache?: RequestCache; + token?: string; +} diff --git a/src/modules/ideation/application/dtos/response.dto.ts b/src/modules/ideation/application/dtos/response.dto.ts new file mode 100644 index 000000000..7157fa3b3 --- /dev/null +++ b/src/modules/ideation/application/dtos/response.dto.ts @@ -0,0 +1,26 @@ +import { type IdeationBodyDto } from "./common.dto"; + +interface IdeationResponseDto { + id: number; + voyageTeamMemberId: number; + createdAt: Date; + updatedAt: Date; +} + +export interface AddIdeationResponseDto + extends IdeationResponseDto, + IdeationBodyDto {} + +export interface EditIdeationResponseDto extends AddIdeationResponseDto {} + +export interface DeleteIdeationResponseDto extends AddIdeationResponseDto {} + +export interface IdeationVoteResponseDto extends IdeationResponseDto { + projectIdeaId: number; +} + +export interface FinalizeIdeationResponseDto + extends IdeationResponseDto, + IdeationBodyDto { + isSelected: boolean; +} diff --git a/src/modules/ideation/application/entities/ideation.ts b/src/modules/ideation/application/entities/ideation.ts new file mode 100644 index 000000000..ac2f2e523 --- /dev/null +++ b/src/modules/ideation/application/entities/ideation.ts @@ -0,0 +1,17 @@ +import { type IdeationVotes } from "./ideationVotes"; +import { type VoyageMember } from "@/store/features/ideation/ideationSlice"; + +// importing the voyage member from the store for now but it would ideally be imported from a different domain in modules folder +export interface Ideation { + id: number; + title: string; + description: string; + vision: string; + isSelected: boolean; + createdAt: Date; + updatedAt: Date; + contributedBy: { + member: VoyageMember; + }; + projectIdeaVotes: IdeationVotes[]; +} diff --git a/src/modules/ideation/application/entities/ideationVotes.ts b/src/modules/ideation/application/entities/ideationVotes.ts new file mode 100644 index 000000000..2b40962c4 --- /dev/null +++ b/src/modules/ideation/application/entities/ideationVotes.ts @@ -0,0 +1,13 @@ +import { type VoyageMember } from "@/store/features/ideation/ideationSlice"; + +// importing the voyage member from the store for now but it would ideally be imported from a different domain in modules folder +export interface IdeationVotes { + id: number; + voyageTeamMemberId: number; + projectIdeaId: number; + createdAt: Date; + updatedAt: Date; + votedBy: { + member: VoyageMember; + }; +} diff --git a/src/modules/ideation/application/usecases/addIdeationUseCase.ts b/src/modules/ideation/application/usecases/addIdeationUseCase.ts new file mode 100644 index 000000000..2afcc8755 --- /dev/null +++ b/src/modules/ideation/application/usecases/addIdeationUseCase.ts @@ -0,0 +1,19 @@ +import { inject, injectable } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { type AddIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type AddIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +@injectable() +export class AddIdeationUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute(props: AddIdeationRequestDto): Promise { + return await this.ideationApi.addIdeation({ + ...props, + }); + } +} diff --git a/src/modules/ideation/application/usecases/addIdeationVoteUseCase.ts b/src/modules/ideation/application/usecases/addIdeationVoteUseCase.ts new file mode 100644 index 000000000..1c604b76b --- /dev/null +++ b/src/modules/ideation/application/usecases/addIdeationVoteUseCase.ts @@ -0,0 +1,20 @@ +import { inject } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationVoteRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type IdeationVoteResponseDto } from "@/modules/ideation/application/dtos/response.dto"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; + +export class AddIdeationVoteUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute( + props: IdeationVoteRequestDto, + ): Promise { + return await this.ideationApi.addIdeationVote({ + ...props, + }); + } +} diff --git a/src/modules/ideation/application/usecases/deleteIdeationUseCase.ts b/src/modules/ideation/application/usecases/deleteIdeationUseCase.ts new file mode 100644 index 000000000..58b56290d --- /dev/null +++ b/src/modules/ideation/application/usecases/deleteIdeationUseCase.ts @@ -0,0 +1,20 @@ +import { inject } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type DeleteIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type DeleteIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; + +export class DeleteIdeationUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute( + props: DeleteIdeationRequestDto, + ): Promise { + return await this.ideationApi.deleteIdeation({ + ...props, + }); + } +} diff --git a/src/modules/ideation/application/usecases/editIdeationUseCase.ts b/src/modules/ideation/application/usecases/editIdeationUseCase.ts new file mode 100644 index 000000000..540c1c203 --- /dev/null +++ b/src/modules/ideation/application/usecases/editIdeationUseCase.ts @@ -0,0 +1,20 @@ +import { inject } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { type EditIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type EditIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export class EditIdeationUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute( + props: EditIdeationRequestDto, + ): Promise { + return await this.ideationApi.editIdeation({ + ...props, + }); + } +} diff --git a/src/modules/ideation/application/usecases/finalizeIdeationUseCase.ts b/src/modules/ideation/application/usecases/finalizeIdeationUseCase.ts new file mode 100644 index 000000000..46364836b --- /dev/null +++ b/src/modules/ideation/application/usecases/finalizeIdeationUseCase.ts @@ -0,0 +1,20 @@ +import { inject } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { type FinalizeIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type FinalizeIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export class FinalizeIdeationUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute( + props: FinalizeIdeationRequestDto, + ): Promise { + return await this.ideationApi.finalizeIdeation({ + ...props, + }); + } +} diff --git a/src/modules/ideation/application/usecases/removeIdeationVoteUseCase.ts b/src/modules/ideation/application/usecases/removeIdeationVoteUseCase.ts new file mode 100644 index 000000000..e1be4f03d --- /dev/null +++ b/src/modules/ideation/application/usecases/removeIdeationVoteUseCase.ts @@ -0,0 +1,20 @@ +import { inject } from "tsyringe"; +import { TYPES } from "@/di/types"; +import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; +import { type IdeationVoteRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type IdeationVoteResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export class RemoveIdeationVoteUseCase { + constructor( + @inject(TYPES.IdeationApiPort) + private readonly ideationApi: IdeationApiPort, + ) {} + + async execute( + props: IdeationVoteRequestDto, + ): Promise { + return await this.ideationApi.removeIdeationVote({ + ...props, + }); + } +} diff --git a/src/modules/ideation/ports/primary/addIdeationVotePort.ts b/src/modules/ideation/ports/primary/addIdeationVotePort.ts new file mode 100644 index 000000000..c5708a48e --- /dev/null +++ b/src/modules/ideation/ports/primary/addIdeationVotePort.ts @@ -0,0 +1,6 @@ +import { type IdeationVoteRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type IdeationVoteResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export interface AddIdeationVotePort { + execute(props: IdeationVoteRequestDto): Promise; +} diff --git a/src/modules/ideation/ports/primary/deleteIdeationPort.ts b/src/modules/ideation/ports/primary/deleteIdeationPort.ts new file mode 100644 index 000000000..ec79ef261 --- /dev/null +++ b/src/modules/ideation/ports/primary/deleteIdeationPort.ts @@ -0,0 +1,6 @@ +import { type DeleteIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type DeleteIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export interface DeleteIdeationPort { + execute(props: DeleteIdeationRequestDto): Promise; +} diff --git a/src/modules/ideation/ports/primary/editIdeationPort.ts b/src/modules/ideation/ports/primary/editIdeationPort.ts new file mode 100644 index 000000000..44fddba47 --- /dev/null +++ b/src/modules/ideation/ports/primary/editIdeationPort.ts @@ -0,0 +1,6 @@ +import { type EditIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type EditIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export interface EditIdeationPort { + execute(props: EditIdeationRequestDto): Promise; +} diff --git a/src/modules/ideation/ports/primary/finalizeIdeationPort.ts b/src/modules/ideation/ports/primary/finalizeIdeationPort.ts new file mode 100644 index 000000000..bd459ffb9 --- /dev/null +++ b/src/modules/ideation/ports/primary/finalizeIdeationPort.ts @@ -0,0 +1,8 @@ +import { type FinalizeIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type FinalizeIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export interface FinalizeIdeationPort { + execute( + props: FinalizeIdeationRequestDto, + ): Promise; +} diff --git a/src/modules/ideation/ports/primary/ideationClientPort.ts b/src/modules/ideation/ports/primary/ideationClientPort.ts new file mode 100644 index 000000000..9efd9db1a --- /dev/null +++ b/src/modules/ideation/ports/primary/ideationClientPort.ts @@ -0,0 +1,11 @@ +import { type AddIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; +import { type AddIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; + +export interface IdeationClientPort { + addIdeation({ + teamId, + title, + description, + vision, + }: AddIdeationRequestDto): Promise; +} diff --git a/src/modules/ideation/ports/primary/removeIdeationVotePort.ts b/src/modules/ideation/ports/primary/removeIdeationVotePort.ts new file mode 100644 index 000000000..f4bdd700c --- /dev/null +++ b/src/modules/ideation/ports/primary/removeIdeationVotePort.ts @@ -0,0 +1,6 @@ +import { type IdeationVoteRequestDto } from "@/modules/ideation/application/dtos/request.dto"; +import { type IdeationVoteResponseDto } from "@/modules/ideation/application/dtos/response.dto"; + +export interface RemoveIdeationVotePort { + execute(props: IdeationVoteRequestDto): Promise; +} diff --git a/src/modules/ideation/ports/secondary/ideationApiPort.ts b/src/modules/ideation/ports/secondary/ideationApiPort.ts new file mode 100644 index 000000000..2174ef961 --- /dev/null +++ b/src/modules/ideation/ports/secondary/ideationApiPort.ts @@ -0,0 +1,36 @@ +import type { + AddIdeationRequestDto, + DeleteIdeationRequestDto, + EditIdeationRequestDto, + FinalizeIdeationRequestDto, + IdeationVoteRequestDto, +} from "@/modules/ideation/application/dtos/request.dto"; + +import type { + AddIdeationResponseDto, + DeleteIdeationResponseDto, + EditIdeationResponseDto, + FinalizeIdeationResponseDto, + IdeationVoteResponseDto, +} from "@/modules/ideation/application/dtos/response.dto"; + +export interface IdeationApiPort { + addIdeation: ( + props: AddIdeationRequestDto, + ) => Promise; + editIdeation: ( + props: EditIdeationRequestDto, + ) => Promise; + deleteIdeation: ( + props: DeleteIdeationRequestDto, + ) => Promise; + addIdeationVote: ( + props: IdeationVoteRequestDto, + ) => Promise; + removeIdeationVote: ( + props: IdeationVoteRequestDto, + ) => Promise; + finalizeIdeation: ( + props: FinalizeIdeationRequestDto, + ) => Promise; +} diff --git a/src/modules/restApi/adapters/secondary/nextJsRestApiAdapter.ts b/src/modules/restApi/adapters/secondary/nextJsRestApiAdapter.ts new file mode 100644 index 000000000..bcb7feed2 --- /dev/null +++ b/src/modules/restApi/adapters/secondary/nextJsRestApiAdapter.ts @@ -0,0 +1,152 @@ +import { type RestApiPort } from "@/modules/restApi/ports/secondary/restApiPort"; +import type { + DeleteParams, + PatchParams, + PostParams, + UnauthPostParams, + GetParams, +} from "@/modules/restApi/application/entities/restApiParams"; +import { type RequestOptions } from "@/modules/restApi/application/entities/requestOptions"; + +type NextJsAuthRequestOptions = Required< + Pick +> & + Omit; + +type NextJsUnAuthRequestionOptions = Required> & + Omit; + +interface NextJsGetParams extends Omit { + options: NextJsAuthRequestOptions; // Enforce required options +} + +interface NextJsPostParams extends Omit, "options"> { + options: NextJsAuthRequestOptions; // Enforce required options +} + +interface NextJsPatchParams extends Omit, "options"> { + options: NextJsAuthRequestOptions; // Enforce required options +} + +interface NextJsDeleteParams extends Omit { + options: NextJsAuthRequestOptions; // Enforce required options +} + +interface NextJsUnauthParams extends Omit, "options"> { + options: NextJsUnAuthRequestionOptions; // Enforce required options +} + +export class NextJsRestApiAdapter implements RestApiPort { + private baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + async get({ url, options }: NextJsGetParams): Promise { + const { token, cache, tags } = options; + + const res = await fetch(`${this.baseUrl}/${url}`, { + method: "GET", + headers: { + Cookie: token, + }, + cache, + next: { + tags: [tags ?? ""], + }, + }); + + if (!res.ok) { + throw new Error(`Status code: ${res.status}, Message: ${res.statusText}`); + } + + return res.json() as Promise; + } + + async post({ url, options, payload }: NextJsPostParams): Promise { + const { token, cache } = options; + + const res = await fetch(`${this.baseUrl}/${url}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Cookie: token, + }, + body: payload ? JSON.stringify(payload) : undefined, + cache, + }); + + if (!res.ok) { + throw new Error(`Status code: ${res.status}, Message: ${res.statusText}`); + } + + return res.json() as Promise; + } + + async patch({ + url, + options, + payload, + }: NextJsPatchParams): Promise { + const { token, cache } = options; + + const res = await fetch(`${this.baseUrl}/${url}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Cookie: token, + }, + body: JSON.stringify(payload), + cache, + }); + + if (!res.ok) { + throw new Error(`Status code: ${res.status}, Message: ${res.statusText}`); + } + + return res.json() as Promise; + } + + async delete({ url, options }: NextJsDeleteParams): Promise { + const { token, cache } = options; + + const res = await fetch(`${this.baseUrl}/${url}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Cookie: token, + }, + cache, + }); + + if (!res.ok) { + throw new Error(`Status code: ${res.status}, Message: ${res.statusText}`); + } + + return res.json() as Promise; + } + + async unauthpost({ + url, + options, + payload, + }: NextJsUnauthParams): Promise { + const { cache } = options; + + const res = await fetch(`${this.baseUrl}/${url}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + cache, + }); + + if (!res.ok) { + throw new Error(`Status code: ${res.status}, Message: ${res.statusText}`); + } + + return res.json() as Promise; + } +} diff --git a/src/modules/restApi/application/entities/requestOptions.ts b/src/modules/restApi/application/entities/requestOptions.ts new file mode 100644 index 000000000..865d13ff3 --- /dev/null +++ b/src/modules/restApi/application/entities/requestOptions.ts @@ -0,0 +1,5 @@ +export interface RequestOptions { + token?: string; + cache?: RequestCache; + tags?: string; +} diff --git a/src/modules/restApi/application/entities/restApiParams.ts b/src/modules/restApi/application/entities/restApiParams.ts new file mode 100644 index 000000000..4155d3d79 --- /dev/null +++ b/src/modules/restApi/application/entities/restApiParams.ts @@ -0,0 +1,29 @@ +import { type RequestOptions } from "./requestOptions"; + +export interface GetParams { + url: string; + options: RequestOptions; +} + +export interface PostParams { + url: string; + options: RequestOptions; + payload?: X; +} + +export interface PatchParams { + url: string; + options: RequestOptions; + payload: X; +} + +export interface DeleteParams { + url: string; + options: RequestOptions; +} + +export interface UnauthPostParams { + url: string; + options: RequestOptions; + payload: X; +} diff --git a/src/modules/restApi/ports/secondary/restApiPort.ts b/src/modules/restApi/ports/secondary/restApiPort.ts new file mode 100644 index 000000000..eba80fff4 --- /dev/null +++ b/src/modules/restApi/ports/secondary/restApiPort.ts @@ -0,0 +1,19 @@ +import type { + DeleteParams, + GetParams, + PatchParams, + PostParams, + UnauthPostParams, +} from "@/modules/restApi/application/entities/restApiParams"; + +export interface RestApiPort { + get(params: GetParams): Promise; + + post(params: PostParams): Promise; + + patch(params: PatchParams): Promise; + + delete(params: DeleteParams): Promise; + + unauthpost(params: UnauthPostParams): Promise; +} diff --git a/src/store/features/modal/modalSlice.ts b/src/store/features/modal/modalSlice.ts index b157c3e9c..282efceaf 100644 --- a/src/store/features/modal/modalSlice.ts +++ b/src/store/features/modal/modalSlice.ts @@ -9,7 +9,7 @@ import { type DeleteIdeationProps, type DeleteIdeationResponse, type deleteIdeation, -} from "@/myVoyage/ideation/ideationService"; +} from "@/myVoyage/ideation/adapters/ideationSA"; import { type DeleteFeatureProps, type deleteFeature, diff --git a/src/types/types.ts b/src/types/types.ts index 2fb4ad92e..cf150593d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,5 +1,4 @@ // only put global types here - export interface AppError { message: string; } diff --git a/src/utils/getCurrentVoyageData.ts b/src/utils/getCurrentVoyageData.ts index 4ec6e78bd..c70a2a77a 100644 --- a/src/utils/getCurrentVoyageData.ts +++ b/src/utils/getCurrentVoyageData.ts @@ -1,7 +1,7 @@ -import { type AsyncActionResponse } from "./handleAsync"; import { getCurrentVoyageTeam } from "./getCurrentVoyageTeam"; -import { type User } from "@/store/features/user/userSlice"; import { type AppError } from "@/types/types"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; +import { type User } from "@/store/features/user/userSlice"; interface GetCurrentVoyageDataProps { teamId: number; diff --git a/src/utils/getUser.ts b/src/utils/getUser.ts index b5c8efe7b..84e5c7b7f 100644 --- a/src/utils/getUser.ts +++ b/src/utils/getUser.ts @@ -1,6 +1,7 @@ import { getAccessToken } from "./getCookie"; -import { type AsyncActionResponse, handleAsync } from "./handleAsync"; +import { handleAsync } from "./handleAsync"; import { GET } from "./requests"; +import { type AsyncActionResponse } from "@/utils/handleAsync"; import { type User } from "@/store/features/user/userSlice"; export function getUser(): Promise> { diff --git a/src/utils/handleAsync.ts b/src/utils/handleAsync.ts index de3264f70..3bf8ffbd6 100644 --- a/src/utils/handleAsync.ts +++ b/src/utils/handleAsync.ts @@ -1,6 +1,6 @@ import { type AppError } from "@/types/types"; -type AsyncFunction = () => Promise; +export type AsyncFunction = () => Promise; export type AsyncActionResponse = [X | null, AppError | null]; export async function handleAsync( diff --git a/tsconfig.json b/tsconfig.json index c396102d3..1f945204a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,8 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "plugins": [ { "name": "next" @@ -29,7 +31,9 @@ "@/*": ["./src/*"], "@/public/*": ["./public/*"], "@/myVoyage/*": ["./src/app/(main)/my-voyage/[teamId]/*"], - "@/dashboard/*": ["./src/app/(main)/dashboard/*"] + "@/dashboard/*": ["./src/app/(main)/dashboard/*"], + "@/di/*": ["./src/di/*"], + "@/modules/*": ["./src/modules/*"] } }, "include": [ diff --git a/yarn.lock b/yarn.lock index 6f51f2de9..b8060fe39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6732,10 +6732,12 @@ __metadata: react-hook-form: "npm:^7.46.2" react-redux: "npm:^8.1.2" redux-persist: "npm:^6.0.0" + reflect-metadata: "npm:^0.2.2" storybook: "npm:^7.6.4" tailwind-merge: "npm:^2.0.0" tailwindcss: "npm:3.3.3" tsconfig-paths-webpack-plugin: "npm:^4.1.0" + tsyringe: "npm:^4.8.0" typescript: "npm:5.3.3" zod: "npm:^3.22.2" languageName: unknown @@ -14223,6 +14225,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 1cd93a15ea291e420204955544637c264c216e7aac527470e393d54b4bb075f10a17e60d8168ec96600c7e0b9fcc0cb0bb6e91c3fbf5b0d8c9056f04e6ac1ec2 + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.4": version: 1.0.4 resolution: "reflect.getprototypeof@npm:1.0.4" @@ -15953,7 +15962,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.13.0, tslib@npm:^1.8.1": +"tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 @@ -15978,6 +15987,15 @@ __metadata: languageName: node linkType: hard +"tsyringe@npm:^4.8.0": + version: 4.8.0 + resolution: "tsyringe@npm:4.8.0" + dependencies: + tslib: "npm:^1.9.3" + checksum: e13810e8ff39c4093acd0649bc5db3c164825827631e1522cd9d5ca8694a018447fa1c24f059ea54e93b1020767b1131b9dc9ce598dabfc9aa41c11544bbfe19 + languageName: node + linkType: hard + "tty-browserify@npm:^0.0.1": version: 0.0.1 resolution: "tty-browserify@npm:0.0.1"