From d8c3ee8499abb9b6a3d5ace07539925c9b9cfdee Mon Sep 17 00:00:00 2001 From: Piotr S Date: Thu, 9 May 2024 15:04:06 +0200 Subject: [PATCH] feat: ASAP-415 Add Working-Group Public API endpoint (#4266) * add new public endpoints * add publish date and version to the responses --- .../working-group.data-provider.ts | 2 + apps/gp2-server/src/publicApp.ts | 20 ++++- .../src/routes/public/working-group.route.ts | 49 +++++++++++ .../test/fixtures/working-group.fixtures.ts | 38 +++++++++ .../routes/public/working-group.route.test.ts | 82 +++++++++++++++++++ packages/fixtures/src/gp2/working-groups.ts | 1 + packages/model/src/gp2/working-group.ts | 17 ++++ 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 apps/gp2-server/src/routes/public/working-group.route.ts create mode 100644 apps/gp2-server/test/routes/public/working-group.route.test.ts diff --git a/apps/gp2-server/src/data-providers/working-group.data-provider.ts b/apps/gp2-server/src/data-providers/working-group.data-provider.ts index c66bc30b6c..61f3dd015d 100644 --- a/apps/gp2-server/src/data-providers/working-group.data-provider.ts +++ b/apps/gp2-server/src/data-providers/working-group.data-provider.ts @@ -143,6 +143,8 @@ export const parseWorkingGroupToDataObject = ( primaryEmail: workingGroup.primaryEmail ?? undefined, secondaryEmail: workingGroup.secondaryEmail ?? undefined, leadingMembers: workingGroup.leadingMembers ?? '', + publishDate: workingGroup.sys.publishedAt, + systemPublishedVersion: workingGroup.sys.publishedVersion || undefined, tags, members, milestones, diff --git a/apps/gp2-server/src/publicApp.ts b/apps/gp2-server/src/publicApp.ts index 0293eaa1ae..46abf86581 100644 --- a/apps/gp2-server/src/publicApp.ts +++ b/apps/gp2-server/src/publicApp.ts @@ -27,6 +27,9 @@ import UserController from './controllers/user.controller'; import { userRouteFactory } from './routes/public/user.route'; import { UserContentfulDataProvider } from './data-providers/user.data-provider'; import { AssetContentfulDataProvider } from './data-providers/asset.data-provider'; +import WorkingGroupController from './controllers/working-group.controller'; +import { workingGroupRouteFactory } from './routes/public/working-group.route'; +import { WorkingGroupContentfulDataProvider } from './data-providers/working-group.data-provider'; export const publicAppFactory = ( dependencies: PublicAppDependencies = {}, @@ -70,12 +73,20 @@ export const publicAppFactory = ( const assetDataProvider = new AssetContentfulDataProvider( getContentfulRestClientFactory, ); + const workingGroupDataProvider = new WorkingGroupContentfulDataProvider( + contentfulGraphQLClient, + getContentfulRestClientFactory, + ); + const userController = dependencies.userController || new UserController(userDataProvider, assetDataProvider); const outputController = dependencies.outputController || new OutputController(outputDataProvider, externalUserDataProvider); + const workingGroupController = + dependencies.workingGroupController || + new WorkingGroupController(workingGroupDataProvider); const basicRoutes = Router(); @@ -86,9 +97,15 @@ export const publicAppFactory = ( const outputRoutes = outputRouteFactory(outputController); const userRoutes = userRouteFactory(userController); + const workingGroupRoutes = workingGroupRouteFactory(workingGroupController); // add routes - app.use('/public', [basicRoutes, outputRoutes, userRoutes]); + app.use('/public', [ + basicRoutes, + outputRoutes, + userRoutes, + workingGroupRoutes, + ]); // Catch all app.get('*', async (_req, res) => { @@ -117,6 +134,7 @@ type PublicAppDependencies = { userDataProvider?: UserDataProvider; userController?: UserController; externalUserDataProvider?: ExternalUserDataProvider; + workingGroupController?: WorkingGroupController; sentryErrorHandler?: typeof Sentry.Handlers.errorHandler; sentryRequestHandler?: typeof Sentry.Handlers.requestHandler; sentryTransactionIdHandler?: RequestHandler; diff --git a/apps/gp2-server/src/routes/public/working-group.route.ts b/apps/gp2-server/src/routes/public/working-group.route.ts new file mode 100644 index 0000000000..1bce507e65 --- /dev/null +++ b/apps/gp2-server/src/routes/public/working-group.route.ts @@ -0,0 +1,49 @@ +import { Router, Response } from 'express'; +import { gp2 as gp2Model } from '@asap-hub/model'; +import WorkingGroupController from '../../controllers/working-group.controller'; + +export const workingGroupRouteFactory = ( + workingGroupController: WorkingGroupController, +): Router => { + const workingGroupRoutes = Router(); + + workingGroupRoutes.get( + '/working-groups', + async (_req, res: Response) => { + const result = await workingGroupController.fetch(); + + res.json({ + total: result.total, + items: result.items.map(mapWorkingGroupToPublicWorkingGroup), + }); + }, + ); + + workingGroupRoutes.get( + '/working-groups/:workingGroupId', + async (req, res: Response) => { + const { workingGroupId } = req.params; + + const workingGroup = + await workingGroupController.fetchById(workingGroupId); + + res.json(mapWorkingGroupToPublicWorkingGroup(workingGroup)); + }, + ); + + return workingGroupRoutes; +}; + +const mapWorkingGroupToPublicWorkingGroup = ( + workingGroup: gp2Model.WorkingGroupResponse, +): gp2Model.PublicWorkingGroupResponse => ({ + id: workingGroup.id, + description: workingGroup.description, + members: workingGroup.members, + shortDescription: workingGroup.shortDescription, + title: workingGroup.title, + primaryEmail: workingGroup.primaryEmail, + secondaryEmail: workingGroup.secondaryEmail, + publishDate: workingGroup.publishDate, + systemPublishedVersion: workingGroup.systemPublishedVersion, +}); diff --git a/apps/gp2-server/test/fixtures/working-group.fixtures.ts b/apps/gp2-server/test/fixtures/working-group.fixtures.ts index a622be46c7..9c8e09298d 100644 --- a/apps/gp2-server/test/fixtures/working-group.fixtures.ts +++ b/apps/gp2-server/test/fixtures/working-group.fixtures.ts @@ -45,6 +45,8 @@ export const getWorkingGroupDataObject = name: 'working group calendar', }, tags: [{ id: '42', name: 'tag-1' }], + publishDate: '2023-05-17T13:39:03.250Z', + systemPublishedVersion: 14, }); export const getWorkingGroupUpdateDataObject = @@ -94,6 +96,39 @@ export const getListWorkingGroupsResponse = items: [getWorkingGroupResponse()], }); +export const getPublicWorkingGroupResponse = + (): gp2Model.PublicWorkingGroupResponse => { + const { + id, + description, + title, + shortDescription, + members, + primaryEmail, + secondaryEmail, + publishDate, + systemPublishedVersion, + } = getWorkingGroupResponse(); + + return { + id, + description, + title, + shortDescription, + primaryEmail, + secondaryEmail, + members, + publishDate, + systemPublishedVersion, + }; + }; + +export const getListPublicWorkingGroupResponse = + (): gp2Model.ListPublicWorkingGroupResponse => ({ + total: 1, + items: [getPublicWorkingGroupResponse()], + }); + export const getContentfulGraphqlWorkingGroupMembers = () => ({ total: 1, items: [ @@ -148,6 +183,9 @@ export const getContentfulGraphqlWorkingGroup = ( > => ({ sys: { id: '11', + publishedAt: '2023-05-17T13:39:03.250Z', + publishedVersion: 14, + firstPublishedAt: '2021-01-01T13:39:03.250Z', }, title: 'a working group title', shortDescription: 'Short description', diff --git a/apps/gp2-server/test/routes/public/working-group.route.test.ts b/apps/gp2-server/test/routes/public/working-group.route.test.ts new file mode 100644 index 0000000000..ca79488083 --- /dev/null +++ b/apps/gp2-server/test/routes/public/working-group.route.test.ts @@ -0,0 +1,82 @@ +import { NotFoundError } from '@asap-hub/errors'; +import supertest from 'supertest'; +import { publicAppFactory } from '../../../src/publicApp'; +import { + getListPublicWorkingGroupResponse, + getListWorkingGroupsResponse, + getPublicWorkingGroupResponse, + getWorkingGroupResponse, +} from '../../fixtures/working-group.fixtures'; +import { workingGroupControllerMock } from '../../mocks/working-group.controller.mock'; + +describe('/working-groups/ route', () => { + const publicApp = publicAppFactory({ + workingGroupController: workingGroupControllerMock, + }); + + afterEach(jest.clearAllMocks); + + describe('GET /working-groups', () => { + test('Should return 200 when no working-group exists', async () => { + workingGroupControllerMock.fetch.mockResolvedValueOnce({ + items: [], + total: 0, + }); + const response = await supertest(publicApp).get('/public/working-groups'); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + total: 0, + items: [], + }); + }); + + test('Should return the results correctly', async () => { + const listWorkingGroupResponse = getListWorkingGroupsResponse(); + const listPublicWorkingGroupResponse = + getListPublicWorkingGroupResponse(); + + workingGroupControllerMock.fetch.mockResolvedValueOnce( + listWorkingGroupResponse, + ); + + const response = await supertest(publicApp).get('/public/working-groups'); + + expect(response.body).toEqual(listPublicWorkingGroupResponse); + }); + }); + + describe('GET /working-groups/:workingGroupId', () => { + test('Should return 404 when working-group does not exist', async () => { + workingGroupControllerMock.fetchById.mockRejectedValueOnce( + new NotFoundError(undefined, 'working-group with id not found'), + ); + + const response = await supertest(publicApp).get( + '/public/working-groups/working-group-id', + ); + + expect(response.status).toBe(404); + expect(response.body).toEqual({ + error: 'Not Found', + message: 'working-group with id not found', + statusCode: 404, + }); + }); + + test('Should return the working-group correctly', async () => { + const workingGroupResponse = getWorkingGroupResponse(); + const publicWorkingGroupResponse = getPublicWorkingGroupResponse(); + + workingGroupControllerMock.fetchById.mockResolvedValueOnce( + workingGroupResponse, + ); + + const response = await supertest(publicApp).get( + `/public/working-groups/${workingGroupResponse!.id}`, + ); + + expect(response.body).toEqual(publicWorkingGroupResponse); + }); + }); +}); diff --git a/packages/fixtures/src/gp2/working-groups.ts b/packages/fixtures/src/gp2/working-groups.ts index b7138c35b7..e4d6f2a23c 100644 --- a/packages/fixtures/src/gp2/working-groups.ts +++ b/packages/fixtures/src/gp2/working-groups.ts @@ -30,6 +30,7 @@ const mockedWorkingGroup: gp2.WorkingGroupResponse = { }, ], tags: [], + publishDate: '2023-05-17T13:39:03.250Z', }; export const createWorkingGroupResponse = ( diff --git a/packages/model/src/gp2/working-group.ts b/packages/model/src/gp2/working-group.ts index c6223b0a12..72d15112cd 100644 --- a/packages/model/src/gp2/working-group.ts +++ b/packages/model/src/gp2/working-group.ts @@ -24,13 +24,30 @@ export type WorkingGroupDataObject = { resources?: Resource[]; calendar?: Calendar; tags: TagDataObject[]; + publishDate: string; + systemPublishedVersion?: number; }; export type ListWorkingGroupDataObject = ListResponse; export type WorkingGroupResponse = WorkingGroupDataObject; +export type PublicWorkingGroupResponse = Pick< + WorkingGroupResponse, + | 'id' + | 'title' + | 'description' + | 'shortDescription' + | 'primaryEmail' + | 'secondaryEmail' + | 'members' + | 'publishDate' + | 'systemPublishedVersion' +>; + export type ListWorkingGroupResponse = ListResponse; +export type ListPublicWorkingGroupResponse = + ListResponse; export type WorkingGroupUpdateDataObject = Partial< Pick