diff --git a/src/lib/infrastructure/ioc/container-config.ts b/src/lib/infrastructure/ioc/container-config.ts index f7b84e646..095f57e87 100644 --- a/src/lib/infrastructure/ioc/container-config.ts +++ b/src/lib/infrastructure/ioc/container-config.ts @@ -37,12 +37,6 @@ import SwitchAccountInputPort from "@/lib/core/port/primary/switch-account-input import SwitchAccountUseCase from "@/lib/core/use-case/switch-account-usecase"; import SwitchAccountController, { ISwitchAccountController } from "../controller/switch-account-controller"; import SwitchAccountPresenter from "../presenter/switch-account-presenter"; -import { ListDIDsInputPort } from "@/lib/core/port/primary/list-dids-ports"; -import ListDIDsUseCase from "@/lib/core/use-case/list-dids-usecase"; -import ListDIDsController, { ListDIDsControllerParameters } from "../controller/list-dids-controller"; -import ListDIDsPresenter from "../presenter/list-dids-presenter"; -import { ListDIDsRequest } from "@/lib/core/usecase-models/list-dids-usecase-models"; -import { BaseController } from "@/lib/sdk/controller"; import { loadFeatures } from "@/lib/sdk/ioc-helpers"; @@ -56,6 +50,8 @@ appContainer.bind(GATEWAYS.DID).to(RucioDIDGateway); appContainer.bind(GATEWAYS.ENV_CONFIG).to(EnvConfigGateway); appContainer.bind(GATEWAYS.STREAM).to(StreamingGateway).inRequestScope(); +loadFeatures(appContainer) + appContainer.bind(INPUT_PORT.USERPASS_LOGIN).to(UserPassLoginUseCase).inRequestScope(); appContainer.bind(CONTROLLERS.USERPASS_LOGIN).to(UserPassLoginController); appContainer.bind>(USECASE_FACTORY.USERPASS_LOGIN).toFactory((context: interfaces.Context) => @@ -66,17 +62,6 @@ appContainer.bind>(USECASE_FACTORY.US } ); -appContainer.bind(INPUT_PORT.LIST_DIDS).to(ListDIDsUseCase).inRequestScope(); -appContainer.bind>(CONTROLLERS.LIST_DIDS).to(ListDIDsController); -appContainer.bind>(USECASE_FACTORY.LIST_DIDS).toFactory((context: interfaces.Context) => - (response: NextApiResponse) => { - const rucioDIDGateway: DIDGatewayOutputPort = appContainer.get(GATEWAYS.DID) - return new ListDIDsUseCase(new ListDIDsPresenter(response), rucioDIDGateway); - } -); - -loadFeatures(appContainer) - appContainer.bind(INPUT_PORT.SET_X509_LOGIN_SESSION).to(SetX509LoginSessionUseCase).inRequestScope(); appContainer.bind(CONTROLLERS.SET_X509_LOGIN_SESSION).to(SetX509LoginSessionController); appContainer.bind>(USECASE_FACTORY.SET_X509_LOGIN_SESSION).toFactory((context: interfaces.Context) => diff --git a/src/lib/infrastructure/ioc/features/list-dids-feature.ts b/src/lib/infrastructure/ioc/features/list-dids-feature.ts new file mode 100644 index 000000000..bf71ff7e7 --- /dev/null +++ b/src/lib/infrastructure/ioc/features/list-dids-feature.ts @@ -0,0 +1,42 @@ +import DIDGatewayOutputPort from "@/lib/core/port/secondary/did-gateway-output-port"; +import { ListDIDsError, ListDIDsRequest, ListDIDsResponse } from "@/lib/core/usecase-models/list-dids-usecase-models"; +import { ListDIDsControllerParameters } from "@/lib/infrastructure/controller/list-dids-controller"; +import { ListDIDsViewModel } from "@/lib/infrastructure/data/view-model/list-did"; +import { BaseStreamableFeature, IOCSymbols } from "@/lib/sdk/ioc-helpers"; +import GATEWAYS from "@/lib/infrastructure/ioc/ioc-symbols-gateway"; +import CONTROLLERS from "@/lib/infrastructure/ioc/ioc-symbols-controllers"; +import INPUT_PORT from "@/lib/infrastructure/ioc/ioc-symbols-input-port"; +import USECASE_FACTORY from "@/lib/infrastructure/ioc/ioc-symbols-usecase-factory"; +import { Container } from "inversify"; +import ListDIDsController from "@/lib/infrastructure/controller/list-dids-controller"; +import ListDIDsUseCase from "@/lib/core/use-case/list-dids-usecase"; +import ListDIDsPresenter from "../../presenter/list-dids-presenter"; + + +export default class ListDidsFeature extends BaseStreamableFeature< + ListDIDsControllerParameters, + ListDIDsRequest, + ListDIDsResponse, + ListDIDsError, + ListDIDsViewModel> { + constructor( appContainer: Container ) { + const didGateway = appContainer.get(GATEWAYS.DID) + const symbols: IOCSymbols = { + CONTROLLER: CONTROLLERS.LIST_DIDS, + USECASE_FACTORY: USECASE_FACTORY.LIST_DIDS, + INPUT_PORT: INPUT_PORT.LIST_DIDS, + } + super( + appContainer, + ListDIDsController, + ListDIDsUseCase, + [ + didGateway, + ], + ListDIDsPresenter, + false, + symbols + ) + } + + } \ No newline at end of file diff --git a/src/lib/sdk/ioc-helpers.ts b/src/lib/sdk/ioc-helpers.ts index 6e921fe99..823eee45f 100644 --- a/src/lib/sdk/ioc-helpers.ts +++ b/src/lib/sdk/ioc-helpers.ts @@ -4,10 +4,12 @@ import { IronSession } from 'iron-session'; import { NextApiResponse } from 'next'; import path from 'path'; import { BaseController, TParameters } from './controller'; -import { BasePresenter } from './presenter'; -import type { BaseInputPort, BaseOutputPort } from './primary-ports'; +import { BasePresenter, BaseStreamingPresenter } from './presenter'; +import type { BaseInputPort, BaseOutputPort, BaseStreamingOutputPort } from './primary-ports'; import { TUseCase } from './usecase'; import TUseCaseFactory from './usecase-factory'; +import { BaseResponseModel } from './usecase-models'; +import { BaseViewModel } from './view-models'; /** * An object that contains symbols for the different types of dependencies in an IoC container. @@ -125,6 +127,100 @@ TControllerParams extends TParameters, } } +/** + * A base class for streamable features in a web application. + * @template TControllerParams The type of the parameters for the controller. + * @template TRequestModel The type of the request model for the use case. + * @template TResponseModel The type of the response model for the use case. + * @template TErrorModel The type of the error model for the use case. + * @template TViewModel The type of the view model for the presenter. + */ +export class BaseStreamableFeature< + TControllerParams extends TParameters, + TRequestModel, + TResponseModel extends BaseResponseModel, + TErrorModel, + TViewModel extends BaseViewModel, +> { + /** + * Creates a new instance of the `BaseStreamableFeature` class. + * @param appContainer The IoC container for the application. + * @param Controller The controller class for the feature. + * @param UseCase The use case class for the feature. + * @param useCaseContructorArgs The arguments to pass to the use case constructor. + * @param Presenter The presenter class for the feature. + * @param passSessionToPresenter Whether to pass the session to the presenter. + * @param symbols An object that contains symbols for the different types of dependencies in the IoC container. + */ + constructor( + appContainer: Container, + Controller: new (useCaseFactory: TUseCaseFactory) => BaseController, + UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, + useCaseContructorArgs: any[] = [], + Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, + passSessionToPresenter: boolean = false, + symbols: IOCSymbols, + ) { + this.createIOCBindings( + appContainer, + Controller, + UseCase, + useCaseContructorArgs, + Presenter, + passSessionToPresenter, + symbols, + ) + } + + /** + * Creates the IoC bindings for the streamable feature. + * @param appContainer The IoC container for the application. + * @param Controller The controller class for the feature. + * @param UseCase The use case class for the feature. + * @param useCaseContructorArgs The arguments to pass to the use case constructor. + * @param Presenter The presenter class for the feature. + * @param passSessionToPresenter Whether to pass the session to the presenter. + * @param symbols An object that contains symbols for the different types of dependencies in the IoC container. + */ + createIOCBindings< + TControllerParams extends TParameters, + TRequestModel, + TResponseModel extends BaseResponseModel, + TErrorModel, + TViewModel extends BaseViewModel, + >( + appContainer: Container, + Controller: new (useCaseFactory: TUseCaseFactory) => BaseController, + UseCase: new (presenter: BaseStreamingOutputPort, ...args: any[]) => TUseCase, + useCaseContructorArgs: any[] = [], + Presenter: new (response: NextApiResponse, session?: IronSession) => BaseStreamingPresenter, + passSessionToPresenter: boolean = false, + symbols: IOCSymbols, + ) { + const symbolInputPort = symbols.INPUT_PORT + const symbolController = symbols.CONTROLLER + const symbolUseCaseFactory = symbols.USECASE_FACTORY + + appContainer.bind>(symbolInputPort).to(UseCase).inRequestScope(); + appContainer.bind>(symbolController).to(Controller).inRequestScope(); + + if(passSessionToPresenter){ + appContainer.bind>>(symbolUseCaseFactory).toFactory, [response: NextApiResponse, session: IronSession]>((context: interfaces.Context) => + (response: NextApiResponse, session: IronSession) => { + const presenter = new Presenter(response, session); + return new UseCase(presenter, ...useCaseContructorArgs); + } + ); + } else { + appContainer.bind>>(symbolUseCaseFactory).toFactory, [response: NextApiResponse]>((context: interfaces.Context) => + (response: NextApiResponse) => { + const presenter = new Presenter(response); + return new UseCase(presenter, ...useCaseContructorArgs); + }); + } + } +} + /** * Loads features from the features directory into the IoC Container. * @param appContainer The IoC container for the application. @@ -144,9 +240,10 @@ export async function loadFeatures(appContainer: Container, featuresDir?: string if (!featureClass) { throw new Error(`Feature ${featureName} has no default export`) } - // if default export is not a subclass of BaseFeature, throw error - if (!(featureClass.prototype instanceof BaseFeature)) { - throw new Error(`Feature ${featureName} is not a subclass of BaseFeature`) + // if default export is not a subclass of BaseFeature or BaseStreambleFeature, throw error + if (!(featureClass.prototype instanceof BaseFeature) && + !(featureClass.prototype instanceof BaseStreamableFeature)) { + throw new Error(`Feature ${featureName} is not a subclass of BaseFeature or BaseStreamableFeature`) } // if constructor signature of default export is not new (appContainer: Container) => BaseFeature, throw error if (featureClass.length !== 1) { diff --git a/src/pages/api/dids.ts b/src/pages/api/dids.ts new file mode 100644 index 000000000..ae40f09c1 --- /dev/null +++ b/src/pages/api/dids.ts @@ -0,0 +1,34 @@ +import appContainer from "@/lib/infrastructure/ioc/container-config"; +import CONTROLLERS from "@/lib/infrastructure/ioc/ioc-symbols-controllers"; +import { BaseController } from "@/lib/sdk/controller"; +import { NextApiRequest, NextApiResponse } from "next"; +import { ListDIDsControllerParameters } from "@/lib/infrastructure/controller/list-dids-controller"; +import { withAuthenticatedSessionRoute } from "@/lib/infrastructure/auth/session-utils"; + +async function listDIDs(req: NextApiRequest, res: NextApiResponse, rucioAuthToken: string) { + if(req.method === 'GET') { + res.status(405).json({ error: 'Method Not Allowed' }) + return + } + const { query, type } = req.body + if(!query) { + res.status(400).json({ error: 'Missing query parameter' }) + return + } + if(!type) { + res.status(400).json({ error: 'Missing type parameter' }) + return + } + + const controller = appContainer.get>(CONTROLLERS.LIST_DIDS) + const controllerParameters: ListDIDsControllerParameters = { + response: res, + query: query, + type: type, + rucioAuthToken: rucioAuthToken + } + + await controller.execute(controllerParameters) +} + +export default withAuthenticatedSessionRoute(listDIDs) \ No newline at end of file