diff --git a/api/src/constants/roles.ts b/api/src/constants/roles.ts index eaee8993df..822182a82f 100644 --- a/api/src/constants/roles.ts +++ b/api/src/constants/roles.ts @@ -2,7 +2,7 @@ * System level roles. * * @export - * @enum {number} + * @enum {string} */ export enum SYSTEM_ROLE { SYSTEM_ADMIN = 'System Administrator', @@ -14,10 +14,22 @@ export enum SYSTEM_ROLE { * Project level roles. * * @export - * @enum {number} + * @enum {string} */ export enum PROJECT_ROLE { - PROJECT_LEAD = 'Project Lead', - PROJECT_EDITOR = 'Editor', - PROJECT_VIEWER = 'Viewer' + COORDINATOR = 'Coordinator', + COLLABORATOR = 'Collaborator', + OBSERVER = 'Observer' +} + +/** + * Role permissions. + * + * @export + * @enum {string} + */ +export enum PROJECT_PERMISSION { + COORDINATOR = 'Coordinator', + COLLABORATOR = 'Collaborator', + OBSERVER = 'Observer' } diff --git a/api/src/database/db.ts b/api/src/database/db.ts index 4ed66f10ed..8aab592509 100644 --- a/api/src/database/db.ts +++ b/api/src/database/db.ts @@ -41,6 +41,15 @@ pg.types.setTypeParser(pg.types.builtins.DATE, (stringValue: string) => { return stringValue; // 1082 for `DATE` type }); +// Adding a TIMESTAMP type parser to keep all dates used in the system consistent +pg.types.setTypeParser(pg.types.builtins.TIMESTAMP, (stringValue: string) => { + return stringValue; // 1082 for `TIMESTAMP` type +}); +// Adding a TIMESTAMPTZ type parser to keep all dates used in the system consistent +pg.types.setTypeParser(pg.types.builtins.TIMESTAMPTZ, (stringValue: string) => { + return stringValue; // 1082 for `DATE` type +}); + // singleton pg pool instance used by the api let DBPool: pg.Pool | undefined; diff --git a/api/src/models/project-view.test.ts b/api/src/models/project-view.test.ts index dca878990b..44be8f89eb 100644 --- a/api/src/models/project-view.test.ts +++ b/api/src/models/project-view.test.ts @@ -23,8 +23,8 @@ describe('ProjectData', () => { project_name: '', project_programs: [], project_types: [], - start_date: new Date('2005-01-01'), - end_date: new Date('2006-01-01'), + start_date: '2005-01-01', + end_date: '2006-01-01', comments: '', revision_count: 1 }; @@ -47,11 +47,11 @@ describe('ProjectData', () => { }); it('sets start_date', () => { - expect(data.start_date).to.eql(new Date('2005-01-01')); + expect(data.start_date).to.eql('2005-01-01'); }); it('sets end_date', () => { - expect(data.end_date).to.eql(new Date('2006-01-01')); + expect(data.end_date).to.eql('2006-01-01'); }); }); @@ -74,8 +74,8 @@ describe('ProjectData', () => { project_name: 'project name', project_programs: [1], project_types: [1, 2], - start_date: new Date('2020-04-20T07:00:00.000Z'), - end_date: new Date('2020-05-20T07:00:00.000Z'), + start_date: '2020-04-20T07:00:00.000Z', + end_date: '2020-05-20T07:00:00.000Z', comments: '', revision_count: 1 }; @@ -98,16 +98,12 @@ describe('ProjectData', () => { }); it('sets start_date', () => { - expect(data.start_date).to.eql(new Date('2020-04-20T07:00:00.000Z')); + expect(data.start_date).to.eql('2020-04-20T07:00:00.000Z'); }); it('sets end_date', () => { - expect(data.end_date).to.eql(new Date('2020-05-20T07:00:00.000Z')); + expect(data.end_date).to.eql('2020-05-20T07:00:00.000Z'); }); - - // it('sets completion_status', () => { - // expect(data.completion_status).to.equal(COMPLETION_STATUS.COMPLETED); - // }); }); }); diff --git a/api/src/models/project-view.ts b/api/src/models/project-view.ts index f1a1f7f947..c46288070f 100644 --- a/api/src/models/project-view.ts +++ b/api/src/models/project-view.ts @@ -30,14 +30,28 @@ export const ProjectData = z.object({ project_name: z.string(), project_programs: z.array(z.number()), project_types: z.array(z.number()), - start_date: z.date(), - end_date: z.date().nullable(), + start_date: z.string(), + end_date: z.string().nullable(), comments: z.string().nullable(), revision_count: z.number() }); export type ProjectData = z.infer; +export const ProjectListData = z.object({ + project_id: z.number(), + uuid: z.string(), + project_name: z.string(), + coordinator_agency: z.string(), + project_programs: z.array(z.number()).default([]), + project_types: z.array(z.number()).default([]), + regions: z.array(z.string()).default([]), + start_date: z.string(), + end_date: z.string().nullable().optional() +}); + +export type ProjectListData = z.infer; + /** * Pre-processes GET /projects/{id} objectives data * diff --git a/api/src/models/user.test.ts b/api/src/models/user.test.ts deleted file mode 100644 index c776cd77f4..0000000000 --- a/api/src/models/user.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { UserObject } from './user'; - -describe('UserObject', () => { - describe('No values provided', () => { - let data: UserObject; - - before(() => { - data = new UserObject((null as unknown) as any); - }); - - it('sets id', function () { - expect(data.id).to.equal(null); - }); - - it('sets user_identifier', function () { - expect(data.user_identifier).to.equal(null); - }); - - it('sets role_names', function () { - expect(data.role_names).to.eql([]); - }); - }); - - describe('valid values provided, no roles', () => { - let data: UserObject; - - const userObject = { system_user_id: 1, user_identifier: 'test name', role_ids: [], role_names: [] }; - - before(() => { - data = new UserObject(userObject); - }); - - it('sets id', function () { - expect(data.id).to.equal(1); - }); - - it('sets user_identifier', function () { - expect(data.user_identifier).to.equal('test name'); - }); - - it('sets role_ids', function () { - expect(data.role_ids).to.eql([]); - }); - - it('sets role_names', function () { - expect(data.role_names).to.eql([]); - }); - }); - - describe('valid values provided', () => { - let data: UserObject; - - const userObject = { - system_user_id: 1, - user_identifier: 'test name', - role_ids: [1, 2], - role_names: ['role 1', 'role 2'] - }; - - before(() => { - data = new UserObject(userObject); - }); - - it('sets id', function () { - expect(data.id).to.equal(1); - }); - - it('sets user_identifier', function () { - expect(data.user_identifier).to.equal('test name'); - }); - - it('sets role_ids', function () { - expect(data.role_ids).to.eql([1, 2]); - }); - - it('sets role_names', function () { - expect(data.role_names).to.eql(['role 1', 'role 2']); - }); - }); -}); diff --git a/api/src/models/user.ts b/api/src/models/user.ts index 98e63af509..ab5f786a5f 100644 --- a/api/src/models/user.ts +++ b/api/src/models/user.ts @@ -1,33 +1,23 @@ -export class UserObject { - id: number; - user_identifier: string; - user_guid: string | null; - identity_source: string; - record_end_date: string; - role_ids: number[]; - role_names: string[]; +import { z } from 'zod'; - constructor(obj?: any) { - this.id = obj?.system_user_id || null; - this.user_identifier = obj?.user_identifier || null; - this.user_guid = obj?.user_guid || null; - this.identity_source = obj?.identity_source || null; - this.record_end_date = obj?.record_end_date || null; - this.role_ids = (obj?.role_ids?.length && obj.role_ids) || []; - this.role_names = (obj?.role_names?.length && obj.role_names) || []; - } -} +export const User = z.object({ + system_user_id: z.number(), + user_identifier: z.string(), + user_guid: z.string().nullable(), + identity_source: z.string(), + record_end_date: z.string().nullable(), + role_ids: z.array(z.number()).default([]), + role_names: z.array(z.string()).default([]) +}); -export class ProjectUserObject { - project_id: number; - system_user_id: number; - project_role_ids: number[]; - project_role_names: string[]; +export type User = z.infer; - constructor(obj?: any) { - this.project_id = obj?.project_id || null; - this.system_user_id = obj?.system_user_id || null; - this.project_role_ids = (obj?.project_role_ids?.length && obj.project_role_ids) || []; - this.project_role_names = (obj?.project_role_names?.length && obj.project_role_names) || []; - } -} +export const ProjectUser = z.object({ + project_id: z.number(), + system_user_id: z.number(), + project_role_ids: z.array(z.number()), + project_role_names: z.array(z.string()), + project_role_permissions: z.array(z.string()) +}); + +export type ProjectUser = z.infer; diff --git a/api/src/paths/administrative-activity.test.ts b/api/src/paths/administrative-activity.test.ts index d407fb6c53..8bffc2dda4 100644 --- a/api/src/paths/administrative-activity.test.ts +++ b/api/src/paths/administrative-activity.test.ts @@ -42,7 +42,7 @@ describe('createAdministrativeActivity', () => { const dbConnectionObj = getMockDBConnection({ systemUserId: () => systemUserId, commit: sinon.stub() }); sinon.stub(db, 'getAPIUserDBConnection').returns(dbConnectionObj); - const mockResponse: ICreateAdministrativeActivity = { id: 2, date: new Date() }; + const mockResponse: ICreateAdministrativeActivity = { id: 2, date: '2023-01-01' }; sinon.stub(AdministrativeActivityService.prototype, 'createPendingAccessRequest').resolves(mockResponse); sinon.stub(AdministrativeActivityService.prototype, 'sendAccessRequestNotificationEmailToAdmin').resolves(); diff --git a/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.test.ts b/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.test.ts index 52a4d64fc9..a3c8d3ade1 100644 --- a/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.test.ts +++ b/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.test.ts @@ -4,7 +4,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { ADMINISTRATIVE_ACTIVITY_STATUS_TYPE } from '../../../../constants/administrative-activity'; import * as db from '../../../../database/db'; -import { UserObject } from '../../../../models/user'; +import { User } from '../../../../models/user'; import { AdministrativeActivityService } from '../../../../services/administrative-activity-service'; import { UserService } from '../../../../services/user-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db'; @@ -65,10 +65,10 @@ describe('approveAccessRequest', () => { const systemUserId = 4; const existingRoleIds = [1, 2]; - const mockSystemUser: UserObject = { - id: systemUserId, + const mockSystemUser: User = { + system_user_id: systemUserId, user_identifier: '', - user_guid: 'aaaa', + user_guid: '', identity_source: 'idir', record_end_date: '', role_ids: existingRoleIds, diff --git a/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.ts b/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.ts index eb32487851..bf99a88d04 100644 --- a/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.ts +++ b/api/src/paths/administrative-activity/system-access/{administrativeActivityId}/approve.ts @@ -144,7 +144,7 @@ export function approveAccessRequest(): RequestHandler { if (rolesIdsToAdd?.length) { // Add any missing roles (if any) - await userService.addUserSystemRoles(systemUserObject.id, rolesIdsToAdd); + await userService.addUserSystemRoles(systemUserObject.system_user_id, rolesIdsToAdd); } // Update the access request record status diff --git a/api/src/paths/draft/create.test.ts b/api/src/paths/draft/create.test.ts index 4300398672..0d21e49a11 100644 --- a/api/src/paths/draft/create.test.ts +++ b/api/src/paths/draft/create.test.ts @@ -57,7 +57,7 @@ describe('paths/draft/create', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }); const requestHandler = create.createDraft(); @@ -71,7 +71,7 @@ describe('paths/draft/create', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }); }); diff --git a/api/src/paths/draft/list.test.ts b/api/src/paths/draft/list.test.ts index bcfcbabdd8..d77dfe5d6e 100644 --- a/api/src/paths/draft/list.test.ts +++ b/api/src/paths/draft/list.test.ts @@ -64,15 +64,15 @@ describe('paths/draft/list', () => { webform_draft_id: 1, name: 'draft object 1', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }, { webform_draft_id: 2, name: 'draft object 2', data: {}, - create_date: ('2022-10-10' as unknown) as Date, - update_date: ('2022-10-11' as unknown) as Date + create_date: '2022-10-10', + update_date: '2022-10-11' } ]); @@ -85,15 +85,15 @@ describe('paths/draft/list', () => { webform_draft_id: 1, name: 'draft object 1', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }, { webform_draft_id: 2, name: 'draft object 2', data: {}, - create_date: ('2022-10-10' as unknown) as Date, - update_date: ('2022-10-11' as unknown) as Date + create_date: '2022-10-10', + update_date: '2022-10-11' } ]); }); diff --git a/api/src/paths/draft/{draftId}/get.test.ts b/api/src/paths/draft/{draftId}/get.test.ts index f6fef86982..9b790cdb49 100644 --- a/api/src/paths/draft/{draftId}/get.test.ts +++ b/api/src/paths/draft/{draftId}/get.test.ts @@ -63,7 +63,7 @@ describe('paths/draft/{draftId}/get', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }); @@ -75,7 +75,7 @@ describe('paths/draft/{draftId}/get', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, + create_date: '2022-10-10', update_date: null }); }); diff --git a/api/src/paths/draft/{draftId}/update.test.ts b/api/src/paths/draft/{draftId}/update.test.ts index 48df3eb0ef..3385437703 100644 --- a/api/src/paths/draft/{draftId}/update.test.ts +++ b/api/src/paths/draft/{draftId}/update.test.ts @@ -64,8 +64,8 @@ describe('paths/draft/{draftId}/update', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, - update_date: ('2022-10-11' as unknown) as Date + create_date: '2022-10-10', + update_date: '2022-10-11' }); const requestHandler = update.updateDraft(); @@ -80,8 +80,8 @@ describe('paths/draft/{draftId}/update', () => { webform_draft_id: 1, name: 'draft object', data: {}, - create_date: ('2022-10-10' as unknown) as Date, - update_date: ('2022-10-11' as unknown) as Date + create_date: '2022-10-10', + update_date: '2022-10-11' }); }); }); diff --git a/api/src/paths/dwc/process.ts b/api/src/paths/dwc/process.ts index 4d6a5ddc0e..f06333bda6 100644 --- a/api/src/paths/dwc/process.ts +++ b/api/src/paths/dwc/process.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../constants/status'; import { getDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.project_id), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/dwc/view-occurrences.ts b/api/src/paths/dwc/view-occurrences.ts index d9f17f1c56..0c23f00382 100644 --- a/api/src/paths/dwc/view-occurrences.ts +++ b/api/src/paths/dwc/view-occurrences.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../constants/status'; import { getDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; @@ -15,9 +15,13 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.body.project_id), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/list.ts b/api/src/paths/project/{projectId}/attachments/list.ts index 8a3f0acca5..10657c6bd1 100644 --- a/api/src/paths/project/{projectId}/attachments/list.ts +++ b/api/src/paths/project/{projectId}/attachments/list.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../services/attachment-service'; @@ -13,9 +13,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/report/upload.ts b/api/src/paths/project/{projectId}/attachments/report/upload.ts index 2be780c77a..3f8142619a 100644 --- a/api/src/paths/project/{projectId}/attachments/report/upload.ts +++ b/api/src/paths/project/{projectId}/attachments/report/upload.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { HTTP400 } from '../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/upload.ts b/api/src/paths/project/{projectId}/attachments/upload.ts index c454ab71bb..7a0aac3dc1 100644 --- a/api/src/paths/project/{projectId}/attachments/upload.ts +++ b/api/src/paths/project/{projectId}/attachments/upload.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; @@ -16,9 +16,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/{attachmentId}/delete.ts b/api/src/paths/project/{projectId}/attachments/{attachmentId}/delete.ts index 07993ca30a..a9d1977395 100644 --- a/api/src/paths/project/{projectId}/attachments/{attachmentId}/delete.ts +++ b/api/src/paths/project/{projectId}/attachments/{attachmentId}/delete.ts @@ -1,8 +1,8 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; -import { UserObject } from '../../../../../models/user'; +import { User } from '../../../../../models/user'; import { authorizeRequestHandler, getSystemUserObject } from '../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../services/attachment-service'; import { getLogger } from '../../../../../utils/logger'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -103,7 +103,7 @@ export function deleteAttachment(): RequestHandler { const attachmentService = new AttachmentService(connection); - const systemUserObject: UserObject = req['system_user'] || (await getSystemUserObject(connection)); + const systemUserObject: User = req['system_user'] || (await getSystemUserObject(connection)); const isAdmin = systemUserObject.role_names.includes(SYSTEM_ROLE.SYSTEM_ADMIN) || systemUserObject.role_names.includes(SYSTEM_ROLE.DATA_ADMINISTRATOR); diff --git a/api/src/paths/project/{projectId}/attachments/{attachmentId}/getSignedUrl.ts b/api/src/paths/project/{projectId}/attachments/{attachmentId}/getSignedUrl.ts index 84e599fb37..0e5b07b373 100644 --- a/api/src/paths/project/{projectId}/attachments/{attachmentId}/getSignedUrl.ts +++ b/api/src/paths/project/{projectId}/attachments/{attachmentId}/getSignedUrl.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../services/attachment-service'; @@ -15,9 +15,9 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts index 83372e303d..39c36faffe 100644 --- a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts +++ b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../services/attachment-service'; @@ -13,9 +13,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/update.ts b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/update.ts index 3241254f9e..7be46cde02 100644 --- a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/update.ts +++ b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/update.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; import { IReportAttachmentAuthor, @@ -18,9 +18,9 @@ export const PUT: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/delete.ts b/api/src/paths/project/{projectId}/delete.ts index faffe9a25f..d618a8f73f 100644 --- a/api/src/paths/project/{projectId}/delete.ts +++ b/api/src/paths/project/{projectId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { getDBConnection } from '../../../database/db'; import { HTTP400 } from '../../../errors/http-error'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; @@ -14,9 +14,9 @@ export const DELETE: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/participants/create.ts b/api/src/paths/project/{projectId}/participants/create.ts index cfed1a6f16..d8b09f6917 100644 --- a/api/src/paths/project/{projectId}/participants/create.ts +++ b/api/src/paths/project/{projectId}/participants/create.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { SYSTEM_IDENTITY_SOURCE } from '../../../../constants/database'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; @@ -16,9 +16,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -164,5 +164,5 @@ export const ensureSystemUserAndProjectParticipantUser = async ( const projectService = new ProjectService(connection); // Add project role, unless they already have one - await projectService.ensureProjectParticipant(projectId, systemUserObject.id, participant.roleId); + await projectService.ensureProjectParticipant(projectId, systemUserObject.system_user_id, participant.roleId); }; diff --git a/api/src/paths/project/{projectId}/participants/get.ts b/api/src/paths/project/{projectId}/participants/get.ts index 9824f9a2eb..319bcb3e38 100644 --- a/api/src/paths/project/{projectId}/participants/get.ts +++ b/api/src/paths/project/{projectId}/participants/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; @@ -14,9 +14,9 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/participants/self.test.ts b/api/src/paths/project/{projectId}/participants/self.test.ts index 524d496f4b..41e5cfdf0f 100644 --- a/api/src/paths/project/{projectId}/participants/self.test.ts +++ b/api/src/paths/project/{projectId}/participants/self.test.ts @@ -6,7 +6,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as db from '../../../../database/db'; import { HTTPError } from '../../../../errors/http-error'; -import { ProjectUserObject } from '../../../../models/user'; +import { ProjectUser } from '../../../../models/user'; import { ProjectService } from '../../../../services/project-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db'; import { GET, getUserRolesForProject } from './self'; @@ -65,11 +65,12 @@ describe('getUserRolesForProject', () => { describe('response validation', () => { const responseValidator = new OpenAPIResponseValidator((GET.apiDoc as unknown) as OpenAPIResponseValidatorArgs); - const mockParticipantRecord: ProjectUserObject = { + const mockParticipantRecord: ProjectUser = { project_id: 1, system_user_id: 20, project_role_ids: [1, 2], - project_role_names: ['RoleA', 'RoleB'] + project_role_names: ['RoleA', 'RoleB'], + project_role_permissions: [] }; describe('should throw an error when', () => { @@ -217,7 +218,8 @@ describe('getUserRolesForProject', () => { project_id: 1, system_user_id: 20, project_role_ids: [1], - project_role_names: ['Test-Role-A'] + project_role_names: ['Test-Role-A'], + project_role_permissions: ['Test Permission'] }); const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); @@ -235,7 +237,8 @@ describe('getUserRolesForProject', () => { project_id: 1, system_user_id: 20, project_role_ids: [1], - project_role_names: ['Test-Role-A'] + project_role_names: ['Test-Role-A'], + project_role_permissions: ['Test Permission'] } }); }); @@ -251,7 +254,8 @@ describe('getUserRolesForProject', () => { project_id: 1, system_user_id: 20, project_role_ids: [1, 2], - project_role_names: ['Test-Role-A', 'Test-Role-B'] + project_role_names: ['Test-Role-A', 'Test-Role-B'], + project_role_permissions: ['Test Permission'] }); const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); @@ -269,7 +273,8 @@ describe('getUserRolesForProject', () => { project_id: 1, system_user_id: 20, project_role_ids: [1, 2], - project_role_names: ['Test-Role-A', 'Test-Role-B'] + project_role_names: ['Test-Role-A', 'Test-Role-B'], + project_role_permissions: ['Test Permission'] } }); }); diff --git a/api/src/paths/project/{projectId}/participants/self.ts b/api/src/paths/project/{projectId}/participants/self.ts index f2e8830542..a821ae10fc 100644 --- a/api/src/paths/project/{projectId}/participants/self.ts +++ b/api/src/paths/project/{projectId}/participants/self.ts @@ -2,7 +2,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { getDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/http-error'; -import { ProjectUserObject } from '../../../../models/user'; import { ProjectService } from '../../../../services/project-service'; import { getLogger } from '../../../../utils/logger'; @@ -31,7 +30,7 @@ GET.apiDoc = { ], responses: { 200: { - description: 'Project particpant roles.', + description: 'Project participant roles.', content: { 'application/json': { schema: { @@ -117,7 +116,7 @@ export function getUserRolesForProject(): RequestHandler { await connection.commit(); return res.status(200).json({ - participant: result ? new ProjectUserObject(result) : null + participant: result }); } catch (error) { defaultLog.error({ label: 'getAllProjectParticipantsSQL', message: 'error', error }); diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts index d5fe64f212..294467cb73 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts @@ -44,7 +44,7 @@ describe('Delete a project participant.', () => { } }); - it('should throw a 400 error when user is only project lead', async () => { + it('should throw a 400 error when user is only coordinator', async () => { const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); const dbConnectionObj = getMockDBConnection(); @@ -74,7 +74,7 @@ describe('Delete a project participant.', () => { } catch (actualError) { expect((actualError as HTTPError).status).to.equal(400); expect((actualError as HTTPError).message).to.equal( - 'Cannot delete project user. User is the only Project Lead for the project.' + 'Cannot delete project user. User is the only Coordinator for the project.' ); } }); diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts index b333c0c6aa..390abfe164 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { HTTP400, HTTP500 } from '../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const DELETE: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -91,25 +91,27 @@ export function deleteProjectParticipant(): RequestHandler { const projectService = new ProjectService(connection); - // Check project lead roles before deleting user + // Check coordinator roles before deleting user const projectParticipantsResponse1 = await projectService.getProjectParticipants(projectId); const projectHasLeadResponse1 = doAllProjectsHaveAProjectLead(projectParticipantsResponse1); const result = await projectService.deleteProjectParticipationRecord(projectParticipationId); if (!result || !result.system_user_id) { - // The delete result is missing necesary data, fail the request + // The delete result is missing necessary data, fail the request throw new HTTP500('Failed to delete project participant'); } - // If Project Lead roles are invalide skip check to prevent removal of only Project Lead of project - // (Project is already missing Project Lead and is in a bad state) + // If coordinator roles are invalid skip check to prevent removal of only coordinator of project + // (Project is already missing coordinator and is in a bad state) if (projectHasLeadResponse1) { const projectParticipantsResponse2 = await projectService.getProjectParticipants(projectId); const projectHasLeadResponse2 = doAllProjectsHaveAProjectLead(projectParticipantsResponse2); if (!projectHasLeadResponse2) { - throw new HTTP400('Cannot delete project user. User is the only Project Lead for the project.'); + throw new HTTP400( + `Cannot delete project user. User is the only ${PROJECT_ROLE.COORDINATOR} for the project.` + ); } } diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.test.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.test.ts index 50d34b86d7..5ff1c12111 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.test.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.test.ts @@ -44,7 +44,7 @@ describe('update a project participant.', () => { } }); - it('should throw a 400 error when user is only project lead', async () => { + it('should throw a 400 error when user is only coordinator', async () => { const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); const dbConnectionObj = getMockDBConnection(); @@ -76,7 +76,7 @@ describe('update a project participant.', () => { } catch (actualError) { expect((actualError as HTTPError).status).to.equal(400); expect((actualError as HTTPError).message).to.equal( - 'Cannot update project user. User is the only Project Lead for the project.' + 'Cannot update project user. User is the only Coordinator for the project.' ); } }); diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts index eaca58fb17..b4fe0de6d2 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { HTTP400, HTTP500 } from '../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const PUT: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -108,7 +108,7 @@ export function updateProjectParticipantRole(): RequestHandler { const projectService = new ProjectService(connection); - // Check project lead roles before updating user + // Check coordinator roles before updating user const projectParticipantsResponse1 = await projectService.getProjectParticipants(Number(req.params.projectId)); const projectHasLeadResponse1 = doAllProjectsHaveAProjectLead(projectParticipantsResponse1); @@ -126,14 +126,16 @@ export function updateProjectParticipantRole(): RequestHandler { roleId ); - // If Project Lead roles are invalid skip check to prevent removal of only Project Lead of project - // (Project is already missing Project Lead and is in a bad state) + // If coordinator roles are invalid skip check to prevent removal of only coordinator of project + // (Project is already missing coordinator and is in a bad state) if (projectHasLeadResponse1) { const projectParticipantsResponse2 = await projectService.getProjectParticipants(Number(req.params.projectId)); const projectHasLeadResponse2 = doAllProjectsHaveAProjectLead(projectParticipantsResponse2); if (!projectHasLeadResponse2) { - throw new HTTP400('Cannot update project user. User is the only Project Lead for the project.'); + throw new HTTP400( + `Cannot update project user. User is the only ${PROJECT_ROLE.COORDINATOR} for the project.` + ); } } diff --git a/api/src/paths/project/{projectId}/survey/create.ts b/api/src/paths/project/{projectId}/survey/create.ts index 641206d6e6..96bf5d37b3 100644 --- a/api/src/paths/project/{projectId}/survey/create.ts +++ b/api/src/paths/project/{projectId}/survey/create.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; import { PostSurveyObject } from '../../../../models/survey-create'; import { GeoJSONFeature } from '../../../../openapi/schemas/geoJson'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/funding-sources/list.ts b/api/src/paths/project/{projectId}/survey/funding-sources/list.ts index adf466d236..5c41e7f7d9 100644 --- a/api/src/paths/project/{projectId}/survey/funding-sources/list.ts +++ b/api/src/paths/project/{projectId}/survey/funding-sources/list.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { HTTP400 } from '../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; @@ -14,9 +14,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/list.ts b/api/src/paths/project/{projectId}/survey/list.ts index 02f6e46d88..d58307651c 100644 --- a/api/src/paths/project/{projectId}/survey/list.ts +++ b/api/src/paths/project/{projectId}/survey/list.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../constants/roles'; import { getDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/http-error'; import { GeoJSONFeature } from '../../../../openapi/schemas/geoJson'; @@ -15,9 +15,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/list.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/list.ts index 278fb57122..0452cde14c 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/list.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/list.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../services/attachment-service'; @@ -13,9 +13,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts index e0ad5f7d36..83f7e1bd50 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/report/upload.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts index f43e176088..096ac82d70 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/upload.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; import { HTTP400 } from '../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; @@ -16,9 +16,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/delete.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/delete.ts index 6c9740c7a2..11a4906284 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/delete.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/delete.ts @@ -1,8 +1,8 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; -import { UserObject } from '../../../../../../../models/user'; +import { User } from '../../../../../../../models/user'; import { authorizeRequestHandler, getSystemUserObject @@ -18,9 +18,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -112,7 +112,7 @@ export function deleteAttachment(): RequestHandler { const attachmentService = new AttachmentService(connection); - const systemUserObject: UserObject = req['system_user'] || (await getSystemUserObject(connection)); + const systemUserObject: User = req['system_user'] || (await getSystemUserObject(connection)); const isAdmin = systemUserObject.role_names.includes(SYSTEM_ROLE.SYSTEM_ADMIN) || systemUserObject.role_names.includes(SYSTEM_ROLE.DATA_ADMINISTRATOR); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/getSignedUrl.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/getSignedUrl.ts index 89e188b014..e404ae6383 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/getSignedUrl.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/getSignedUrl.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../../services/attachment-service'; @@ -15,9 +15,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts index d0b9269b12..63978e854f 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../../../../services/attachment-service'; @@ -13,9 +13,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/update.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/update.ts index 518a012c9f..4584627108 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/update.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/update.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { ATTACHMENT_TYPE } from '../../../../../../../../constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { PutReportAttachmentMetadata } from '../../../../../../../../models/project-survey-attachments'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const PUT: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts index b0b440738e..6d25961609 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { AttachmentService } from '../../../../../services/attachment-service'; @@ -16,9 +16,9 @@ export const DELETE: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.ts index 747ef9d161..163cb3044a 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../../../../../../constants/status'; import { getDBConnection } from '../../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; @@ -15,9 +15,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/upload.ts index 2847ff062d..0c924350ce 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/upload.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/delete.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/delete.ts index 6be5535db1..6beefc8c3e 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/delete.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; import { OccurrenceService } from '../../../../../../../../services/occurrence-service'; @@ -13,9 +13,9 @@ export const DELETE: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/getSignedUrl.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/getSignedUrl.ts index 666ae3c249..53d66b1902 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/getSignedUrl.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/getSignedUrl.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; @@ -18,9 +18,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/get.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/get.ts index d441ecaedc..979179d6da 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/get.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../errors/http-error'; import { ISummarySubmissionMessagesResponse } from '../../../../../../../repositories/summary-repository'; @@ -16,9 +16,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts index 8181f4b076..0256e02a59 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../constants/roles'; import { SUMMARY_SUBMISSION_MESSAGE_TYPE } from '../../../../../../../constants/status'; import { getDBConnection } from '../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../errors/http-error'; @@ -17,9 +17,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/delete.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/delete.ts index a81bf65fc2..f9f02c01c3 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/delete.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; @@ -14,9 +14,9 @@ export const DELETE: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/getSignedUrl.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/getSignedUrl.ts index 66531d4aa3..a37dbcff7a 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/getSignedUrl.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/getSignedUrl.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; @@ -16,9 +16,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/view.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/view.ts index 635fe73a73..bbbd4b6b31 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/view.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/{summaryId}/view.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../../../database/db'; import { HTTP400, HTTP500 } from '../../../../../../../../errors/http-error'; import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization'; @@ -19,9 +19,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts index 6eaa73fef8..ea615cdc15 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { PutSurveyObject } from '../../../../../models/survey-update'; import { GeoJSONFeature } from '../../../../../openapi/schemas/geoJson'; @@ -15,9 +15,9 @@ export const PUT: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update/get.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update/get.ts index b84dcf5f84..4e3b8b1c27 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update/get.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update/get.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles'; import { getDBConnection } from '../../../../../../database/db'; import { GeoJSONFeature } from '../../../../../../openapi/schemas/geoJson'; import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization'; @@ -14,9 +14,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts index edc007f5ae..86fb61125d 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts @@ -167,9 +167,9 @@ describe('survey/{surveyId}/view', () => { survey_metadata_publish: { survey_metadata_publish_id: 1, survey_id: 1, - event_timestamp: new Date('2020-04-04'), + event_timestamp: '2020-04-04', queue_id: 1, - create_date: new Date('2020-04-04'), + create_date: '2020-04-04', create_user: 1, update_date: null, update_user: null, @@ -203,9 +203,9 @@ describe('survey/{surveyId}/view', () => { survey_metadata_publish: { survey_metadata_publish_id: 1, survey_id: 1, - event_timestamp: new Date('2020-04-04'), + event_timestamp: '2020-04-04', queue_id: 1, - create_date: new Date('2020-04-04'), + create_date: '2020-04-04', create_user: 1, update_date: null, update_user: null, diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts index d11641936d..764a3b837a 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../constants/roles'; import { getDBConnection } from '../../../../../database/db'; import { GeoJSONFeature } from '../../../../../openapi/schemas/geoJson'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; @@ -14,9 +14,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/update.ts b/api/src/paths/project/{projectId}/update.ts index c00bd562fe..9d0081513f 100644 --- a/api/src/paths/project/{projectId}/update.ts +++ b/api/src/paths/project/{projectId}/update.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { Feature } from 'geojson'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { getDBConnection } from '../../../database/db'; import { HTTP400 } from '../../../errors/http-error'; import { GeoJSONFeature } from '../../../openapi/schemas/geoJson'; @@ -17,9 +17,9 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -117,7 +117,8 @@ GET.apiDoc = { end_date: { type: 'string', format: 'date', - description: 'ISO 8601 date string for the project end date' + description: 'ISO 8601 date string for the project end date', + nullable: true }, revision_count: { type: 'number' @@ -360,9 +361,9 @@ export const PUT: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/project/{projectId}/view.ts b/api/src/paths/project/{projectId}/view.ts index ea687e178b..d8a9dec8b2 100644 --- a/api/src/paths/project/{projectId}/view.ts +++ b/api/src/paths/project/{projectId}/view.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { getDBConnection } from '../../../database/db'; import { GeoJSONFeature } from '../../../openapi/schemas/geoJson'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; @@ -14,9 +14,13 @@ export const GET: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], + validProjectPermissions: [ + PROJECT_PERMISSION.COORDINATOR, + PROJECT_PERMISSION.COLLABORATOR, + PROJECT_PERMISSION.OBSERVER + ], projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], @@ -101,7 +105,8 @@ GET.apiDoc = { end_date: { type: 'string', format: 'date', - description: 'ISO 8601 date string for the project end date' + description: 'ISO 8601 date string for the project end date', + nullable: true }, comments: { type: 'string', diff --git a/api/src/paths/publish/attachment/resubmit.ts b/api/src/paths/publish/attachment/resubmit.ts index 5bae06c918..62e0b78c69 100644 --- a/api/src/paths/publish/attachment/resubmit.ts +++ b/api/src/paths/publish/attachment/resubmit.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE } from '../../../constants/roles'; +import { PROJECT_PERMISSION } from '../../../constants/roles'; import { getDBConnection } from '../../../database/db'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; import { GCNotifyService } from '../../../services/gcnotify-service'; @@ -13,9 +13,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' } ] }; diff --git a/api/src/paths/publish/project.ts b/api/src/paths/publish/project.ts index 74ec843930..dffdde90a4 100644 --- a/api/src/paths/publish/project.ts +++ b/api/src/paths/publish/project.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { getDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; import { PlatformService } from '../../services/platform-service'; @@ -17,9 +17,9 @@ export const POST: Operation = [ discriminator: 'SystemRole' }, { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' } ] }; diff --git a/api/src/paths/publish/survey.ts b/api/src/paths/publish/survey.ts index eb17550ea9..c0f79ef82e 100644 --- a/api/src/paths/publish/survey.ts +++ b/api/src/paths/publish/survey.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { getDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; import { PlatformService } from '../../services/platform-service'; @@ -17,9 +17,9 @@ export const POST: Operation = [ discriminator: 'SystemRole' }, { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.projectId), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' } ] }; diff --git a/api/src/paths/user/add.test.ts b/api/src/paths/user/add.test.ts index e8461fe867..65f4896b91 100644 --- a/api/src/paths/user/add.test.ts +++ b/api/src/paths/user/add.test.ts @@ -5,7 +5,7 @@ import sinonChai from 'sinon-chai'; import { SYSTEM_IDENTITY_SOURCE } from '../../constants/database'; import * as db from '../../database/db'; import { HTTPError } from '../../errors/http-error'; -import { UserObject } from '../../models/user'; +import { User } from '../../models/user'; import { UserService } from '../../services/user-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; import * as user from './add'; @@ -124,8 +124,8 @@ describe('user', () => { roleId: 1 }; - const mockUserObject: UserObject = { - id: 1, + const mockUserObject: User = { + system_user_id: 1, user_identifier: '', user_guid: '', identity_source: '', @@ -159,10 +159,10 @@ describe('user', () => { roleId: 1 }; - const mockUserObject: UserObject = { - id: 1, + const mockUserObject: User = { + system_user_id: 1, user_identifier: '', - user_guid: null, + user_guid: '', identity_source: '', record_end_date: '', role_ids: [1], diff --git a/api/src/paths/user/add.ts b/api/src/paths/user/add.ts index 773143d021..2e74a06066 100644 --- a/api/src/paths/user/add.ts +++ b/api/src/paths/user/add.ts @@ -123,7 +123,7 @@ export function addSystemRoleUser(): RequestHandler { const userObject = await userService.ensureSystemUser(userGuid, userIdentifier, identitySource); if (userObject) { - await userService.addUserSystemRoles(userObject.id, [roleId]); + await userService.addUserSystemRoles(userObject.system_user_id, [roleId]); } await connection.commit(); diff --git a/api/src/paths/user/list.test.ts b/api/src/paths/user/list.test.ts index 0c8f748700..1fd0331009 100644 --- a/api/src/paths/user/list.test.ts +++ b/api/src/paths/user/list.test.ts @@ -24,13 +24,13 @@ describe('users', () => { const mockResponse = [ { - id: 1, + system_user_id: 1, user_identifier: 'identifier', user_guid: 'aaaa', identity_source: 'idir', record_end_date: '', role_ids: [1, 2], - role_names: ['System Admin', 'Project Lead'] + role_names: ['System Admin', 'Coordinator'] } ]; diff --git a/api/src/paths/user/list.ts b/api/src/paths/user/list.ts index 360a5717cd..76f91af0bf 100644 --- a/api/src/paths/user/list.ts +++ b/api/src/paths/user/list.ts @@ -40,9 +40,9 @@ GET.apiDoc = { items: { title: 'User Response Object', type: 'object', - required: ['id', 'user_identifier', 'identity_source', 'role_ids', 'role_names'], + required: ['system_user_id', 'user_identifier', 'identity_source', 'role_ids', 'role_names'], properties: { - id: { + system_user_id: { type: 'number' }, user_guid: { diff --git a/api/src/paths/user/self.test.ts b/api/src/paths/user/self.test.ts index 593c205987..9907ae6aa8 100644 --- a/api/src/paths/user/self.test.ts +++ b/api/src/paths/user/self.test.ts @@ -41,11 +41,11 @@ describe('getUser', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'identifier', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -54,7 +54,7 @@ describe('getUser', () => { await requestHandler(mockReq, mockRes, mockNext); - expect(mockRes.jsonValue.id).to.equal(1); + expect(mockRes.jsonValue.system_user_id).to.equal(1); expect(mockRes.jsonValue.user_identifier).to.equal('identifier'); expect(mockRes.jsonValue.role_ids).to.eql([1, 2]); expect(mockRes.jsonValue.role_names).to.eql(['role 1', 'role 2']); diff --git a/api/src/paths/user/self.ts b/api/src/paths/user/self.ts index 70ade40e23..0df9af0294 100644 --- a/api/src/paths/user/self.ts +++ b/api/src/paths/user/self.ts @@ -37,9 +37,9 @@ GET.apiDoc = { schema: { title: 'User Response Object', type: 'object', - required: ['id', 'user_identifier', 'user_guid', 'record_end_date', 'role_ids', 'role_names'], + required: ['system_user_id', 'user_identifier', 'user_guid', 'record_end_date', 'role_ids', 'role_names'], properties: { - id: { + system_user_id: { description: 'user id', type: 'integer', minimum: 1 diff --git a/api/src/paths/user/{userId}/delete.test.ts b/api/src/paths/user/{userId}/delete.test.ts index e012cba894..8f50bdb847 100644 --- a/api/src/paths/user/{userId}/delete.test.ts +++ b/api/src/paths/user/{userId}/delete.test.ts @@ -17,7 +17,7 @@ describe('removeSystemUser', () => { sinon.restore(); }); - it('should throw a 400 error if the user is the only Project Lead role on one or more projects', async () => { + it('should throw a 400 error if the user is the only Coordinator role on one or more projects', async () => { const dbConnectionObj = getMockDBConnection(); const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); @@ -33,21 +33,21 @@ describe('removeSystemUser', () => { project_id: 3, system_user_id: 33, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 57, project_id: 1, system_user_id: 33, project_role_id: 3, - project_role_name: 'Viewer' + project_role_name: 'Observer' }, { project_participation_id: 40, project_id: 1, system_user_id: 27, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -63,7 +63,7 @@ describe('removeSystemUser', () => { } catch (actualError) { expect((actualError as HTTPError).status).to.equal(400); expect((actualError as HTTPError).message).to.equal( - 'Cannot remove user. User is the only Project Lead for one or more projects.' + 'Cannot remove user. User is the only Coordinator for one or more projects.' ); } }); @@ -81,7 +81,7 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', @@ -115,11 +115,11 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -152,11 +152,11 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -187,11 +187,11 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -224,11 +224,11 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -262,11 +262,11 @@ describe('removeSystemUser', () => { sinon.stub(delete_endpoint, 'checkIfUserIsOnlyProjectLeadOnAnyProject').resolves(); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'testname', user_guid: 'aaaa', identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [1, 2], role_names: ['role 1', 'role 2'] }); @@ -284,9 +284,9 @@ describe('removeSystemUser', () => { }); describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { - describe('user has Project Lead role', () => { + describe('user has Coordinator role', () => { describe('user is on 1 project', () => { - it('should return false if the user is not the only Project Lead role', () => { + it('should return false if the user is not the only Coordinator role', () => { const userId = 10; const rows = [ @@ -295,14 +295,14 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -311,7 +311,7 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { expect(result).to.equal(true); }); - it('should return true if the user is the only Project Lead role', () => { + it('should return true if the user is the only Coordinator role', () => { const userId = 10; const rows = [ @@ -320,14 +320,14 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' // Only Project Lead on project 1 + project_role_name: 'Coordinator' // Only Coordinator on project 1 }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; @@ -338,7 +338,7 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { }); describe('user is on multiple projects', () => { - it('should return true if the user is not the only Project Lead on all projects', () => { + it('should return true if the user is not the only Coordinator on all projects', () => { const userId = 10; const rows = [ @@ -347,28 +347,28 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 1, system_user_id: 2, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 1, project_id: 2, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 2, system_user_id: 2, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -377,38 +377,38 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { expect(result).to.equal(true); }); - it('should return false if the user the only Project Lead on any project', () => { + it('should return false if the user the only Coordinator on any project', () => { const userId = 10; - // User is on 1 project, and is not the only Project Lead + // User is on 1 project, and is not the only Coordinator const rows = [ { project_participation_id: 1, project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 1, system_user_id: 2, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 1, project_id: 2, system_user_id: userId, project_role_id: 1, - project_role_name: 'Project Lead' // Only Project Lead on project 2 + project_role_name: 'Coordinator' // Only Coordinator on project 2 }, { project_participation_id: 2, project_id: 2, system_user_id: 2, project_role_id: 1, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; @@ -419,7 +419,7 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { }); }); - describe('user does not have Project Lead role', () => { + describe('user does not have Coordinator role', () => { describe('user is on 1 project', () => { it('should return true', () => { const userId = 10; @@ -430,14 +430,14 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -457,28 +457,28 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: userId, project_role_id: 1, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 1, system_user_id: 2, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 1, project_id: 2, system_user_id: userId, project_role_id: 1, - project_role_name: 'Viewer' + project_role_name: 'Observer' }, { project_participation_id: 2, project_id: 2, system_user_id: 2, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -499,14 +499,14 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { project_id: 1, system_user_id: 20, project_role_id: 1, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 1, system_user_id: 30, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' } ]; @@ -518,21 +518,21 @@ describe('doAllProjectsHaveAProjectLeadIfUserIsRemoved', () => { }); describe('doAllProjectsHaveAProjectLead', () => { - it('should return false if no user has Project Lead role', () => { + it('should return false if no user has Coordinator role', () => { const rows = [ { project_participation_id: 1, project_id: 1, system_user_id: 10, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; @@ -541,21 +541,21 @@ describe('doAllProjectsHaveAProjectLead', () => { expect(result).to.equal(false); }); - it('should return true if one Project Lead role exists per project', () => { + it('should return true if one Coordinator role exists per project', () => { const rows = [ { project_participation_id: 1, project_id: 1, system_user_id: 12, project_role_id: 1, - project_role_name: 'Project Lead' // Only Project Lead on project 1 + project_role_name: 'Coordinator' // Only Coordinator on project 1 }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; @@ -564,35 +564,35 @@ describe('doAllProjectsHaveAProjectLead', () => { expect(result).to.equal(true); }); - it('should return true if one Project Lead exists on all projects', () => { + it('should return true if one Coordinator exists on all projects', () => { const rows = [ { project_participation_id: 1, project_id: 1, system_user_id: 10, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 1, system_user_id: 2, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 1, project_id: 2, system_user_id: 10, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 2, system_user_id: 2, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; @@ -601,35 +601,35 @@ describe('doAllProjectsHaveAProjectLead', () => { expect(result).to.equal(true); }); - it('should return false if no Project Lead exists on any one project', () => { + it('should return false if no Coordinator exists on any one project', () => { const rows = [ { project_participation_id: 1, project_id: 1, system_user_id: 10, project_role_id: 1, - project_role_name: 'Project Lead' + project_role_name: 'Coordinator' }, { project_participation_id: 2, project_id: 1, system_user_id: 20, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 1, project_id: 2, system_user_id: 10, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' }, { project_participation_id: 2, project_id: 2, system_user_id: 20, project_role_id: 2, - project_role_name: 'Editor' + project_role_name: 'Collaborator' } ]; diff --git a/api/src/paths/user/{userId}/delete.ts b/api/src/paths/user/{userId}/delete.ts index b8e673d867..ba73b34565 100644 --- a/api/src/paths/user/{userId}/delete.ts +++ b/api/src/paths/user/{userId}/delete.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../../constants/roles'; +import { PROJECT_PERMISSION, PROJECT_ROLE, SYSTEM_ROLE } from '../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../database/db'; import { HTTP400 } from '../../../errors/http-error'; import { queries } from '../../../queries/queries'; @@ -113,7 +113,7 @@ export const checkIfUserIsOnlyProjectLeadOnAnyProject = async (userId: number, c userId ); - // No projects associated to user, skip Project Lead role check + // No projects associated to user, skip coordinator role check if (!getAllParticipantsResponse.length) { return; } @@ -121,7 +121,7 @@ export const checkIfUserIsOnlyProjectLeadOnAnyProject = async (userId: number, c const onlyProjectLeadResponse = doAllProjectsHaveAProjectLeadIfUserIsRemoved(getAllParticipantsResponse, userId); if (!onlyProjectLeadResponse) { - throw new HTTP400('Cannot remove user. User is the only Project Lead for one or more projects.'); + throw new HTTP400(`Cannot remove user. User is the only ${PROJECT_ROLE.COORDINATOR} for one or more projects.`); } }; @@ -136,21 +136,21 @@ export const deleteAllProjectRoles = async (userId: number, connection: IDBConne }; /** - * Given an array of project participation role objects, return false if any project has no Project Lead role. Return + * Given an array of project participation role objects, return false if any project has no Coordinator role. Return * true otherwise. * * @param {any[]} rows * @return {*} {boolean} */ export const doAllProjectsHaveAProjectLead = (rows: any[]): boolean => { - // No project with project lead + // No project with Coordinator if (!rows.length) { return false; } const projectLeadsPerProject: { [key: string]: any } = {}; - // count how many Project Lead roles there are per project + // count how many coordinator roles there are per project rows.forEach((row) => { const key = row.project_id; @@ -158,27 +158,27 @@ export const doAllProjectsHaveAProjectLead = (rows: any[]): boolean => { projectLeadsPerProject[key] = 0; } - if (row.project_role_name === PROJECT_ROLE.PROJECT_LEAD) { + if (row.project_role_name === PROJECT_PERMISSION.COORDINATOR) { projectLeadsPerProject[key] += 1; } }); const projectLeadCounts = Object.values(projectLeadsPerProject); - // check if any projects would be left with no Project Lead + // check if any projects would be left with no Coordinator for (const count of projectLeadCounts) { if (!count) { - // found a project with no Project Lead + // found a project with no Coordinator return false; } } - // all projects have a Project Lead + // all projects have a Coordinator return true; }; /** - * Given an array of project participation role objects, return true if any project has no Project Lead role after + * Given an array of project participation role objects, return true if any project has no Coordinator role after * removing all rows associated with the provided `userId`. Return false otherwise. * * @param {any[]} rows @@ -186,14 +186,14 @@ export const doAllProjectsHaveAProjectLead = (rows: any[]): boolean => { * @return {*} {boolean} */ export const doAllProjectsHaveAProjectLeadIfUserIsRemoved = (rows: any[], userId: number): boolean => { - // No project with project lead + // No project with coordinator if (!rows.length) { return false; } const projectLeadsPerProject: { [key: string]: any } = {}; - // count how many Project Lead roles there are per project + // count how many Coordinator roles there are per project rows.forEach((row) => { const key = row.project_id; @@ -201,21 +201,21 @@ export const doAllProjectsHaveAProjectLeadIfUserIsRemoved = (rows: any[], userId projectLeadsPerProject[key] = 0; } - if (row.system_user_id !== userId && row.project_role_name === PROJECT_ROLE.PROJECT_LEAD) { + if (row.system_user_id !== userId && row.project_role_name === PROJECT_PERMISSION.COORDINATOR) { projectLeadsPerProject[key] += 1; } }); const projectLeadCounts = Object.values(projectLeadsPerProject); - // check if any projects would be left with no Project Lead + // check if any projects would be left with no Coordinator for (const count of projectLeadCounts) { if (!count) { - // found a project with no Project Lead + // found a project with no Coordinator return false; } } - // all projects have a Project Lead + // all projects have a Coordinator return true; }; diff --git a/api/src/paths/user/{userId}/get.test.ts b/api/src/paths/user/{userId}/get.test.ts index 19f6170670..452dd3ea93 100644 --- a/api/src/paths/user/{userId}/get.test.ts +++ b/api/src/paths/user/{userId}/get.test.ts @@ -51,9 +51,9 @@ describe('user', () => { }; sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [], role_names: [], user_guid: 'aaaa', @@ -65,9 +65,9 @@ describe('user', () => { await requestHandler(mockReq, mockRes, mockNext); expect(mockRes.jsonValue).to.eql({ - id: 1, + system_user_id: 1, identity_source: 'idir', - record_end_date: '', + record_end_date: null, role_ids: [], role_names: [], user_guid: 'aaaa', diff --git a/api/src/paths/user/{userId}/get.ts b/api/src/paths/user/{userId}/get.ts index 1524006b96..fa147b5ecb 100644 --- a/api/src/paths/user/{userId}/get.ts +++ b/api/src/paths/user/{userId}/get.ts @@ -49,9 +49,9 @@ GET.apiDoc = { schema: { title: 'User Response Object', type: 'object', - required: ['id', 'user_identifier', 'user_guid', 'record_end_date', 'role_ids', 'role_names'], + required: ['system_user_id', 'user_identifier', 'user_guid', 'record_end_date', 'role_ids', 'role_names'], properties: { - id: { + system_user_id: { description: 'user id', type: 'integer', minimum: 1 diff --git a/api/src/paths/user/{userId}/system-roles/update.test.ts b/api/src/paths/user/{userId}/system-roles/update.test.ts index 3a3c252499..f3925e8bad 100644 --- a/api/src/paths/user/{userId}/system-roles/update.test.ts +++ b/api/src/paths/user/{userId}/system-roles/update.test.ts @@ -30,7 +30,7 @@ describe('updateSystemRolesHandler', () => { }; sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'test name', user_guid: 'aaaa', identity_source: 'idir', @@ -77,7 +77,7 @@ describe('updateSystemRolesHandler', () => { }); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'test name', user_guid: 'aaaa', identity_source: 'idir', @@ -114,7 +114,7 @@ describe('updateSystemRolesHandler', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'test name', user_guid: 'aaaa', identity_source: 'idir', @@ -158,7 +158,7 @@ describe('updateSystemRolesHandler', () => { }); sinon.stub(UserService.prototype, 'getUserById').resolves({ - id: 1, + system_user_id: 1, user_identifier: 'test name', user_guid: 'aaaa', identity_source: 'idir', diff --git a/api/src/paths/xlsx/process.ts b/api/src/paths/xlsx/process.ts index 4fe56b0899..193f03016a 100644 --- a/api/src/paths/xlsx/process.ts +++ b/api/src/paths/xlsx/process.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../constants/status'; import { getDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; @@ -15,9 +15,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.project_id), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/xlsx/transform.ts b/api/src/paths/xlsx/transform.ts index b4f9973919..a493ef8c34 100644 --- a/api/src/paths/xlsx/transform.ts +++ b/api/src/paths/xlsx/transform.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../constants/status'; import { getDBConnection } from '../../database/db'; import { HTTP400 } from '../../errors/http-error'; @@ -16,9 +16,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.project_id), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/paths/xlsx/validate.ts b/api/src/paths/xlsx/validate.ts index 1cde20beb8..881ab9b3fe 100644 --- a/api/src/paths/xlsx/validate.ts +++ b/api/src/paths/xlsx/validate.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../constants/roles'; import { SUBMISSION_STATUS_TYPE } from '../../constants/status'; import { getDBConnection } from '../../database/db'; import { HTTP400 } from '../../errors/http-error'; @@ -16,9 +16,9 @@ export const POST: Operation = [ return { or: [ { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR], + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR], projectId: Number(req.body.project_id), - discriminator: 'ProjectRole' + discriminator: 'ProjectPermission' }, { validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR], diff --git a/api/src/repositories/administrative-activity-repository.ts b/api/src/repositories/administrative-activity-repository.ts index b3e4e8d56d..7e20d6609a 100644 --- a/api/src/repositories/administrative-activity-repository.ts +++ b/api/src/repositories/administrative-activity-repository.ts @@ -31,14 +31,14 @@ export const IAdministrativeActivity = z.object({ description: z.string().nullable(), data: JsonSchema, notes: z.string().nullable(), - create_date: z.union([z.date(), z.string()]) + create_date: z.string() }); export type IAdministrativeActivity = z.infer; export const ICreateAdministrativeActivity = z.object({ id: z.number(), - date: z.date() + date: z.string() }); export type ICreateAdministrativeActivity = z.infer; diff --git a/api/src/repositories/draft-repository.ts b/api/src/repositories/draft-repository.ts index 0cdaf92e86..ee6d6eb2ae 100644 --- a/api/src/repositories/draft-repository.ts +++ b/api/src/repositories/draft-repository.ts @@ -8,8 +8,8 @@ export const WebformDraft = z.object({ webform_draft_id: z.number(), name: z.string(), data: jsonSchema, - create_date: z.date(), - update_date: z.date().nullable() + create_date: z.string(), + update_date: z.string().nullable() }); export type WebformDraft = z.infer; diff --git a/api/src/repositories/history-publish-repository.ts b/api/src/repositories/history-publish-repository.ts index ec30d21c12..5d60785ce5 100644 --- a/api/src/repositories/history-publish-repository.ts +++ b/api/src/repositories/history-publish-repository.ts @@ -47,11 +47,11 @@ export interface ISurveyReportPublish { export const ProjectMetadataPublish = z.object({ project_metadata_publish_id: z.number(), project_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), queue_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -61,11 +61,11 @@ export type ProjectMetadataPublish = z.infer; export const SurveyMetadataPublish = z.object({ survey_metadata_publish_id: z.number(), survey_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), queue_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -75,11 +75,11 @@ export type SurveyMetadataPublish = z.infer; export const OccurrenceSubmissionPublish = z.object({ occurrence_submission_publish_id: z.number(), occurrence_submission_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), queue_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -89,11 +89,11 @@ export type OccurrenceSubmissionPublish = z.infer; export const ProjectAttachmentPublish = z.object({ project_attachment_publish_id: z.number(), project_attachment_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -117,11 +117,11 @@ export type ProjectAttachmentPublish = z.infer; export const SurveyReportPublish = z.object({ survey_report_publish_id: z.number(), survey_report_attachment_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -131,11 +131,11 @@ export type SurveyReportPublish = z.infer; export const SurveyAttachmentPublish = z.object({ survey_attachment_publish_id: z.number(), survey_attachment_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -145,11 +145,11 @@ export type SurveyAttachmentPublish = z.infer; export const SurveySummarySubmissionPublish = z.object({ survey_summary_submission_publish_id: z.number(), survey_summary_submission_id: z.number(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number(), - create_date: z.date(), + create_date: z.string(), create_user: z.number(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number() }); @@ -166,13 +166,13 @@ export const SurveyAttachmentWithPublishData = z.object({ description: z.string(), key: z.string(), file_size: z.number(), - create_date: z.date().nullable(), + create_date: z.string().nullable(), create_user: z.number().nullable(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number(), uuid: z.string(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number() }); @@ -188,13 +188,13 @@ export const SurveyReportWithPublishData = z.object({ year: z.number(), key: z.string(), file_size: z.number(), - create_date: z.date().nullable(), + create_date: z.string().nullable(), create_user: z.number().nullable(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number(), uuid: z.string(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number() }); @@ -210,13 +210,13 @@ export const ProjectAttachmentWithPublishData = z.object({ description: z.string(), key: z.string(), file_size: z.number(), - create_date: z.date().nullable(), + create_date: z.string().nullable(), create_user: z.number().nullable(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number(), uuid: z.string(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number() }); @@ -232,13 +232,13 @@ export const ProjectReportWithPublishData = z.object({ year: z.number(), key: z.string(), file_size: z.number(), - create_date: z.date().nullable(), + create_date: z.string().nullable(), create_user: z.number().nullable(), - update_date: z.date().nullable(), + update_date: z.string().nullable(), update_user: z.number().nullable(), revision_count: z.number(), uuid: z.string(), - event_timestamp: z.date(), + event_timestamp: z.string(), artifact_revision_id: z.number() }); diff --git a/api/src/repositories/permit-repository.ts b/api/src/repositories/permit-repository.ts index 9c51104619..b18442f93a 100644 --- a/api/src/repositories/permit-repository.ts +++ b/api/src/repositories/permit-repository.ts @@ -79,7 +79,7 @@ export class PermitRepository extends BaseRepository { AND p2.project_id = pp.project_id AND - pr."name" in ('${PROJECT_ROLE.PROJECT_LEAD}', '${PROJECT_ROLE.PROJECT_EDITOR}') + pr."name" in ('${PROJECT_ROLE.COORDINATOR}', '${PROJECT_ROLE.COLLABORATOR}') AND pp.project_role_id = pr.project_role_id AND diff --git a/api/src/repositories/postgis-repository.ts b/api/src/repositories/postgis-repository.ts index 8552bbf6ac..33d19d304f 100644 --- a/api/src/repositories/postgis-repository.ts +++ b/api/src/repositories/postgis-repository.ts @@ -22,7 +22,11 @@ export class PostgisRepository extends BaseRepository { const queryBuilder = knex .queryBuilder() .select( - knex.raw(`ST_AsText(ST_TRANSFORM(ST_GeomFromGeoJSON('${JSON.stringify(geometry)}'), ${srid})) as geometry`) + knex.raw( + `public.ST_AsText(public.ST_TRANSFORM(public.ST_Force2D(public.ST_GeomFromGeoJSON('${JSON.stringify( + geometry + )}')), ${srid})) as geometry` + ) ); const response = await this.connection.knex(queryBuilder, z.object({ geometry: z.string() })); diff --git a/api/src/repositories/project-participation-repository.test.ts b/api/src/repositories/project-participation-repository.test.ts index dbe01e6434..e0fd5f9a2b 100644 --- a/api/src/repositories/project-participation-repository.test.ts +++ b/api/src/repositories/project-participation-repository.test.ts @@ -38,15 +38,22 @@ describe('ProjectParticipationRepository', () => { describe('getProjectParticipant', () => { it('should return result', async () => { - const mockResponse = ({ rows: [{ id: 1 }], rowCount: 1 } as any) as Promise>; - const dbConnection = getMockDBConnection({ query: () => mockResponse }); + const mockResponse = ({ + rows: [ + { + system_user_id: 1 + } + ], + rowCount: 1 + } as any) as Promise>; + const dbConnection = getMockDBConnection({ sql: () => mockResponse }); const repository = new ProjectParticipationRepository(dbConnection); const response = await repository.getProjectParticipant(1, 1); expect(response).to.not.be.null; - expect(response).to.eql({ id: 1 }); + expect(response).to.eql({ system_user_id: 1 }); }); it('should return null', async () => { diff --git a/api/src/repositories/project-participation-repository.ts b/api/src/repositories/project-participation-repository.ts index 0c93864326..1a46c997e4 100644 --- a/api/src/repositories/project-participation-repository.ts +++ b/api/src/repositories/project-participation-repository.ts @@ -1,6 +1,6 @@ import SQL from 'sql-template-strings'; import { ApiExecuteSQLError } from '../errors/api-error'; -import { ProjectUserObject } from '../models/user'; +import { ProjectUser } from '../models/user'; import { BaseRepository } from './base-repository'; /** @@ -33,24 +33,25 @@ export class ProjectParticipationRepository extends BaseRepository { return response.rows[0]; } - async getProjectParticipant(projectId: number, systemUserId: number): Promise { + async getProjectParticipant(projectId: number, systemUserId: number): Promise { const sqlStatement = SQL` SELECT pp.project_id, pp.system_user_id, su.record_end_date, array_remove(array_agg(pr.project_role_id), NULL) AS project_role_ids, - array_remove(array_agg(pr.name), NULL) AS project_role_names + array_remove(array_agg(pr.name), NULL) AS project_role_names, + array_remove(array_agg(pp2.name), NULL) as project_role_permissions FROM project_participation pp - LEFT JOIN - project_role pr - ON - pp.project_role_id = pr.project_role_id - LEFT JOIN - system_user su - ON - pp.system_user_id = su.system_user_id + LEFT JOIN project_role pr + ON pp.project_role_id = pr.project_role_id + LEFT JOIN project_role_permission prp + ON pp.project_role_id = prp.project_role_id + LEFT JOIN project_permission pp2 + ON pp2.project_permission_id = prp.project_permission_id + LEFT JOIN system_user su + ON pp.system_user_id = su.system_user_id WHERE pp.project_id = ${projectId} AND @@ -63,7 +64,7 @@ export class ProjectParticipationRepository extends BaseRepository { su.record_end_date ; `; - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + const response = await this.connection.sql(sqlStatement, ProjectUser); const result = (response && response.rows && response.rows[0]) || null; diff --git a/api/src/repositories/project-repository.ts b/api/src/repositories/project-repository.ts index 05e07433a3..f711bbffe1 100644 --- a/api/src/repositories/project-repository.ts +++ b/api/src/repositories/project-repository.ts @@ -18,7 +18,8 @@ import { GetObjectivesData, GetReportAttachmentsData, IProjectAdvancedFilters, - ProjectData + ProjectData, + ProjectListData } from '../models/project-view'; import { queries } from '../queries/queries'; import { BaseRepository } from './base-repository'; @@ -186,13 +187,15 @@ export class ProjectRepository extends BaseRepository { isUserAdmin: boolean, systemUserId: number | null, filterFields: IProjectAdvancedFilters - ): Promise { + ): Promise { const sqlStatement = SQL` SELECT - p.project_id as id, - p.name, + p.project_id, + p.name as project_name, + p.uuid, p.start_date, p.end_date, + p.revision_count, p.coordinator_agency_name as coordinator_agency, array_remove(array_agg(DISTINCT rl.region_name), null) as regions, array_agg(distinct p2.program_id) as project_programs @@ -282,7 +285,9 @@ export class ProjectRepository extends BaseRepository { p.name, p.start_date, p.end_date, - p.coordinator_agency_name + p.coordinator_agency_name, + p.uuid, + p.revision_count `); /* @@ -312,7 +317,7 @@ export class ProjectRepository extends BaseRepository { sqlStatement.append(';'); - const response = await this.connection.sql(sqlStatement); + const response = await this.connection.sql(sqlStatement, ProjectListData); if (!response.rows) { return []; } @@ -362,7 +367,7 @@ export class ProjectRepository extends BaseRepository { p.project_id = ${projectId}; `; - const response = await this.connection.sql(getProjectSqlStatement); + const response = await this.connection.sql(getProjectSqlStatement, ProjectData); if (response?.rowCount < 1) { throw new ApiExecuteSQLError('Failed to get project data', [ diff --git a/api/src/repositories/user-repository.ts b/api/src/repositories/user-repository.ts index ae02805fa3..5ef6b256f7 100644 --- a/api/src/repositories/user-repository.ts +++ b/api/src/repositories/user-repository.ts @@ -1,18 +1,9 @@ import SQL from 'sql-template-strings'; import { SYSTEM_IDENTITY_SOURCE } from '../constants/database'; import { ApiExecuteSQLError } from '../errors/api-error'; +import { User } from '../models/user'; import { BaseRepository } from './base-repository'; -export interface IGetUser { - system_user_id: number; - user_guid: string; - user_identifier: string; - identity_source: string; - record_end_date: string | null; - role_ids: number[]; - role_names: string[]; -} - export interface IInsertUser { system_user_id: number; user_identity_source_id: number; @@ -52,10 +43,10 @@ export class UserRepository extends BaseRepository { * * * @param {number} systemUserId - * @return {*} {Promise} + * @return {*} {Promise} * @memberof UserRepository */ - async getUserById(systemUserId: number): Promise { + async getUserById(systemUserId: number): Promise { const sqlStatement = SQL` SELECT su.system_user_id, @@ -91,7 +82,7 @@ export class UserRepository extends BaseRepository { su.user_identifier; `; - const response = await this.connection.sql(sqlStatement); + const response = await this.connection.sql(sqlStatement, User); if (response.rowCount !== 1) { throw new ApiExecuteSQLError('Failed to get user by id', [ @@ -109,7 +100,7 @@ export class UserRepository extends BaseRepository { * @return {*} {Promise} * @memberof UserRepository */ - async getUserByGuid(userGuid: string): Promise { + async getUserByGuid(userGuid: string): Promise { const sqlStatement = SQL` SELECT su.system_user_id, @@ -143,7 +134,7 @@ export class UserRepository extends BaseRepository { uis.name; `; - const response = await this.connection.sql(sqlStatement); + const response = await this.connection.sql(sqlStatement, User); return response.rows; } @@ -157,7 +148,7 @@ export class UserRepository extends BaseRepository { * search criteria. * @memberof UserService */ - async getUserByIdentifier(userIdentifier: string, identitySource: string): Promise { + async getUserByIdentifier(userIdentifier: string, identitySource: string): Promise { const sqlStatement = SQL` SELECT su.system_user_id, @@ -193,7 +184,7 @@ export class UserRepository extends BaseRepository { uis.name; `; - const response = await this.connection.sql(sqlStatement); + const response = await this.connection.sql(sqlStatement, User); return response.rows; } @@ -206,10 +197,14 @@ export class UserRepository extends BaseRepository { * @param {string | null} userGuid * @param {string} userIdentifier * @param {string} identitySource - * @return {*} {Promise} + * @return {*} {Promise} * @memberof UserRepository */ - async addSystemUser(userGuid: string | null, userIdentifier: string, identitySource: string): Promise { + async addSystemUser( + userGuid: string | null, + userIdentifier: string, + identitySource: string + ): Promise<{ system_user_id: number }> { const sqlStatement = SQL` INSERT INTO system_user @@ -233,21 +228,17 @@ export class UserRepository extends BaseRepository { now() ) RETURNING - system_user_id, - user_identity_source_id, - user_identifier, - record_effective_date, - record_end_date; + system_user_id; `; - const response = await this.connection.sql(sqlStatement); - - if (response.rowCount !== 1) { + const newUserResponse = await this.connection.sql<{ system_user_id: number }>(sqlStatement); + if (newUserResponse.rowCount !== 1) { throw new ApiExecuteSQLError('Failed to insert new user', [ 'UserRepository->addSystemUser', 'rowCount was null or undefined, expected rowCount = 1' ]); } - return response.rows[0]; + + return newUserResponse.rows[0]; } /** @@ -256,7 +247,7 @@ export class UserRepository extends BaseRepository { * @return {*} {Promise} * @memberof UserRepository */ - async listSystemUsers(): Promise { + async listSystemUsers(): Promise { const sqlStatement = SQL` SELECT su.system_user_id, @@ -289,7 +280,7 @@ export class UserRepository extends BaseRepository { su.user_identifier, uis.name; `; - const response = await this.connection.sql(sqlStatement); + const response = await this.connection.sql(sqlStatement, User); return response.rows; } diff --git a/api/src/request-handlers/security/authorization.test.ts b/api/src/request-handlers/security/authorization.test.ts index a6addb1671..ab5c838010 100644 --- a/api/src/request-handlers/security/authorization.test.ts +++ b/api/src/request-handlers/security/authorization.test.ts @@ -7,7 +7,7 @@ import sinonChai from 'sinon-chai'; import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; import * as db from '../../database/db'; import { HTTPError } from '../../errors/http-error'; -import { ProjectUserObject, UserObject } from '../../models/user'; +import { ProjectUser, User } from '../../models/user'; import { UserService } from '../../services/user-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; import * as authorization from './authorization'; @@ -73,7 +73,7 @@ describe('authorizeRequest', function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserObject = (undefined as unknown) as UserObject; + const mockSystemUserObject = (undefined as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockSystemUserObject); const mockReq = ({ authorization_scheme: {} } as unknown) as Request; @@ -86,7 +86,7 @@ describe('authorizeRequest', function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserObject = ({ role_names: [] } as unknown) as UserObject; + const mockSystemUserObject = ({ role_names: [] } as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockSystemUserObject); sinon.stub(authorization, 'authorizeSystemAdministrator').resolves(true); @@ -101,7 +101,7 @@ describe('authorizeRequest', function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserObject = ({ role_names: [] } as unknown) as UserObject; + const mockSystemUserObject = ({ role_names: [] } as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockSystemUserObject); sinon.stub(authorization, 'authorizeSystemAdministrator').resolves(false); @@ -116,7 +116,7 @@ describe('authorizeRequest', function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserObject = ({ role_names: [] } as unknown) as UserObject; + const mockSystemUserObject = ({ role_names: [] } as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockSystemUserObject); sinon.stub(authorization, 'authorizeSystemAdministrator').resolves(false); @@ -133,7 +133,7 @@ describe('authorizeRequest', function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserObject = ({ role_names: [] } as unknown) as UserObject; + const mockSystemUserObject = ({ role_names: [] } as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockSystemUserObject); sinon.stub(authorization, 'authorizeSystemAdministrator').resolves(false); @@ -244,7 +244,7 @@ describe('executeAuthorizeConfig', function () { discriminator: 'SystemRole' }, { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectRoles: [PROJECT_ROLE.COORDINATOR], projectId: 1, discriminator: 'ProjectRole' }, @@ -291,7 +291,7 @@ describe('authorizeBySystemRole', function () { }; const mockDBConnection = getMockDBConnection(); - const mockGetSystemUsersObjectResponse = (null as unknown) as UserObject; + const mockGetSystemUsersObjectResponse = (null as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); const isAuthorizedBySystemRole = await authorization.authorizeBySystemRole( @@ -377,7 +377,7 @@ describe('authorizeByProjectRole', function () { it('returns false if `authorizeProjectRoles.projectId` is null', async function () { const mockReq = ({} as unknown) as Request; const mockAuthorizeProjectRoles: authorization.AuthorizeByProjectRoles = { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectRoles: [PROJECT_ROLE.COORDINATOR], projectId: (null as unknown) as number, discriminator: 'ProjectRole' }; @@ -413,13 +413,13 @@ describe('authorizeByProjectRole', function () { it('returns false if it fails to fetch the users project role information', async function () { const mockReq = ({} as unknown) as Request; const mockAuthorizeProjectRoles: authorization.AuthorizeByProjectRoles = { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectRoles: [PROJECT_ROLE.COORDINATOR], projectId: 1, discriminator: 'ProjectRole' }; const mockDBConnection = getMockDBConnection(); - const mockProjectUserObject = (undefined as unknown) as ProjectUserObject; + const mockProjectUserObject = (undefined as unknown) as ProjectUser; sinon.stub(authorization, 'getProjectUserObject').resolves(mockProjectUserObject); const isAuthorizedBySystemRole = await authorization.authorizeByProjectRole( @@ -432,10 +432,10 @@ describe('authorizeByProjectRole', function () { }); it('returns false if the user does not have any valid roles', async function () { - const mockProjectUserObject = ({ project_role_names: [] } as unknown) as ProjectUserObject; + const mockProjectUserObject = ({ project_role_names: [] } as unknown) as ProjectUser; const mockReq = ({ project_user: mockProjectUserObject } as unknown) as Request; const mockAuthorizeProjectRoles: authorization.AuthorizeByProjectRoles = { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectRoles: [PROJECT_ROLE.COORDINATOR], projectId: 1, discriminator: 'ProjectRole' }; @@ -451,10 +451,12 @@ describe('authorizeByProjectRole', function () { }); it('returns true if the user has at lest one of the valid roles', async function () { - const mockProjectUserObject = ({ project_role_names: [PROJECT_ROLE.PROJECT_LEAD] } as unknown) as ProjectUserObject; + const mockProjectUserObject = ({ + project_role_names: [PROJECT_ROLE.COORDINATOR] + } as unknown) as ProjectUser; const mockReq = ({ project_user: mockProjectUserObject } as unknown) as Request; const mockAuthorizeProjectRoles: authorization.AuthorizeByProjectRoles = { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], + validProjectRoles: [PROJECT_ROLE.COORDINATOR], projectId: 1, discriminator: 'ProjectRole' }; @@ -479,7 +481,7 @@ describe('authorizeBySystemUser', function () { const mockReq = ({} as unknown) as Request; const mockDBConnection = getMockDBConnection(); - const mockGetSystemUsersObjectResponse = (null as unknown) as UserObject; + const mockGetSystemUsersObjectResponse = (null as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); const isAuthorizedBySystemRole = await authorization.authorizeBySystemUser(mockReq, mockDBConnection); @@ -491,7 +493,7 @@ describe('authorizeBySystemUser', function () { const mockReq = ({ system_user: {} } as unknown) as Request; const mockDBConnection = getMockDBConnection(); - const mockGetSystemUsersObjectResponse = (null as unknown) as UserObject; + const mockGetSystemUsersObjectResponse = (null as unknown) as User; sinon.stub(authorization, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); const isAuthorizedBySystemRole = await authorization.authorizeBySystemUser(mockReq, mockDBConnection); @@ -648,11 +650,19 @@ describe('getSystemUserObject', function () { } }); - it('returns a `UserObject`', async function () { + it('returns a `User`', async function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserWithRolesResponse = new UserObject(); + const mockSystemUserWithRolesResponse = ({ + system_user_id: 1, + user_identifier: 'identifier', + user_guid: 'aaaa', + identity_source: 'idir', + record_end_date: null, + role_ids: [1, 2], + role_names: ['role 1', 'role 2'] + } as unknown) as User; sinon.stub(authorization, 'getSystemUserWithRoles').resolves(mockSystemUserWithRolesResponse); const systemUserObject = await authorization.getSystemUserObject(mockDBConnection); @@ -675,11 +685,11 @@ describe('getSystemUserWithRoles', function () { expect(result).to.be.null; }); - it('returns a UserObject', async function () { + it('returns a User', async function () { const mockDBConnection = getMockDBConnection({ systemUserId: () => 1 }); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockUsersByIdSQLResponse = new UserObject(); + const mockUsersByIdSQLResponse = (null as unknown) as User; sinon.stub(UserService.prototype, 'getUserById').resolves(mockUsersByIdSQLResponse); const result = await authorization.getSystemUserWithRoles(mockDBConnection); @@ -726,16 +736,24 @@ describe('getProjectUserObject', function () { } }); - it('returns a `ProjectUserObject`', async function () { + it('returns a `ProjectUser`', async function () { const mockDBConnection = getMockDBConnection(); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const mockSystemUserWithRolesResponse = {}; + const mockSystemUserWithRolesResponse = { + system_user_id: 1, + user_identifier: 'identifier', + user_guid: 'aaaa', + identity_source: 'idir', + record_end_date: null, + role_ids: [1, 2], + role_names: ['role 1', 'role 2'] + } as any; sinon.stub(authorization, 'getProjectUserWithRoles').resolves(mockSystemUserWithRolesResponse); const systemUserObject = await authorization.getProjectUserObject(1, mockDBConnection); - expect(systemUserObject).to.be.instanceOf(ProjectUserObject); + expect(systemUserObject).to.be.eql(mockSystemUserWithRolesResponse); }); }); @@ -756,7 +774,7 @@ describe('getProjectUserWithRoles', function () { it('returns the first row of the response', async function () { const mockResponseRow = { 'Test Column': 'Test Value' }; const mockQueryResponse = ({ rowCount: 1, rows: [mockResponseRow] } as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ systemUserId: () => 1, query: async () => mockQueryResponse }); + const mockDBConnection = getMockDBConnection({ systemUserId: () => 1, sql: async () => mockQueryResponse }); sinon.stub(db, 'getDBConnection').returns(mockDBConnection); const result = await authorization.getProjectUserWithRoles(1, mockDBConnection); diff --git a/api/src/request-handlers/security/authorization.ts b/api/src/request-handlers/security/authorization.ts index 4f642909d7..73a904132d 100644 --- a/api/src/request-handlers/security/authorization.ts +++ b/api/src/request-handlers/security/authorization.ts @@ -1,9 +1,9 @@ import { Request, RequestHandler } from 'express'; import SQL from 'sql-template-strings'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; +import { PROJECT_PERMISSION, PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; import { getDBConnection, IDBConnection } from '../../database/db'; import { HTTP403, HTTP500 } from '../../errors/http-error'; -import { ProjectUserObject, UserObject } from '../../models/user'; +import { ProjectUser, User } from '../../models/user'; import { UserService } from '../../services/user-service'; import { getLogger } from '../../utils/logger'; @@ -25,11 +25,21 @@ export interface AuthorizeByProjectRoles { discriminator: 'ProjectRole'; } +export interface AuthorizeByProjectPermissions { + validProjectPermissions: PROJECT_PERMISSION[]; + projectId: number; + discriminator: 'ProjectPermission'; +} + export interface AuthorizeBySystemUser { discriminator: 'SystemUser'; } -export type AuthorizeRule = AuthorizeBySystemRoles | AuthorizeByProjectRoles | AuthorizeBySystemUser; +export type AuthorizeRule = + | AuthorizeBySystemRoles + | AuthorizeByProjectRoles + | AuthorizeBySystemUser + | AuthorizeByProjectPermissions; export type AuthorizeConfigOr = { [AuthorizeOperator.AND]?: never; @@ -153,6 +163,9 @@ export const executeAuthorizeConfig = async ( case 'ProjectRole': authorizeResults.push(await authorizeByProjectRole(req, authorizeRule, connection).catch(() => false)); break; + case 'ProjectPermission': + authorizeResults.push(await authorizeByProjectPermission(req, authorizeRule, connection).catch(() => false)); + break; case 'SystemUser': authorizeResults.push(await authorizeBySystemUser(req, connection).catch(() => false)); break; @@ -169,7 +182,7 @@ export const executeAuthorizeConfig = async ( * @return {*} {boolean} `true` if the user is a system administrator, `false` otherwise. */ export const authorizeSystemAdministrator = async (req: Request, connection: IDBConnection): Promise => { - const systemUserObject: UserObject = req['system_user'] || (await getSystemUserObject(connection)); + const systemUserObject: User = req['system_user'] || (await getSystemUserObject(connection)); // Add the system_user to the request for future use, if needed req['system_user'] = systemUserObject; @@ -200,7 +213,7 @@ export const authorizeBySystemRole = async ( return false; } - const systemUserObject: UserObject = req['system_user'] || (await getSystemUserObject(connection)); + const systemUserObject: User = req['system_user'] || (await getSystemUserObject(connection)); // Add the system_user to the request for future use, if needed req['system_user'] = systemUserObject; @@ -219,6 +232,35 @@ export const authorizeBySystemRole = async ( return userHasValidRole(authorizeSystemRoles.validSystemRoles, systemUserObject?.role_names); }; +export const authorizeByProjectPermission = async ( + req: Request, + authorizeProjectPermissions: AuthorizeByProjectPermissions, + connection: IDBConnection +): Promise => { + if (!authorizeProjectPermissions?.projectId) { + // No project id to verify roles for + return false; + } + + if (!authorizeProjectPermissions?.validProjectPermissions.length) { + // No valid rules specified + return true; + } + + const projectUserObject: ProjectUser = + req['project_user'] || (await getProjectUserObject(authorizeProjectPermissions.projectId, connection)); + + // Add the project_user to the request for future use, if needed + req['project_user'] = projectUserObject; + + if (!projectUserObject) { + defaultLog.warn({ label: 'getProjectUser', message: 'project user was null' }); + return false; + } + + return userHasValidRole(authorizeProjectPermissions.validProjectPermissions, projectUserObject.project_role_names); +}; + /** * Check that the user has at least on of the valid project roles specified in `authorizeProjectRoles.validProjectRoles`. * @@ -233,7 +275,7 @@ export const authorizeByProjectRole = async ( authorizeProjectRoles: AuthorizeByProjectRoles, connection: IDBConnection ): Promise => { - if (!authorizeProjectRoles || !authorizeProjectRoles.projectId) { + if (!authorizeProjectRoles?.projectId) { // No project id to verify roles for return false; } @@ -243,7 +285,7 @@ export const authorizeByProjectRole = async ( return true; } - const projectUserObject: ProjectUserObject = + const projectUserObject: ProjectUser = req['project_user'] || (await getProjectUserObject(authorizeProjectRoles.projectId, connection)); // Add the project_user to the request for future use, if needed @@ -265,7 +307,7 @@ export const authorizeByProjectRole = async ( * @return {*} {Promise} `Promise` if the user is a valid system user, `Promise` otherwise. */ export const authorizeBySystemUser = async (req: Request, connection: IDBConnection): Promise => { - const systemUserObject: UserObject = req['system_user'] || (await getSystemUserObject(connection)); + const systemUserObject: User = req['system_user'] || (await getSystemUserObject(connection)); // Add the system_user to the request for future use, if needed req['system_user'] = systemUserObject; @@ -309,7 +351,7 @@ export const userHasValidRole = function (validRoles: string | string[], userRol return false; }; -export const getSystemUserObject = async (connection: IDBConnection): Promise => { +export const getSystemUserObject = async (connection: IDBConnection): Promise => { let systemUserWithRoles; try { @@ -332,7 +374,7 @@ export const getSystemUserObject = async (connection: IDBConnection): Promise)} * @return {*} */ -export const getSystemUserWithRoles = async (connection: IDBConnection): Promise => { +export const getSystemUserWithRoles = async (connection: IDBConnection): Promise => { const systemUserId = connection.systemUserId(); if (!systemUserId) { @@ -344,10 +386,7 @@ export const getSystemUserWithRoles = async (connection: IDBConnection): Promise return userService.getUserById(systemUserId); }; -export const getProjectUserObject = async ( - projectId: number, - connection: IDBConnection -): Promise => { +export const getProjectUserObject = async (projectId: number, connection: IDBConnection): Promise => { let projectUserWithRoles; try { @@ -360,7 +399,7 @@ export const getProjectUserObject = async ( throw new HTTP500('project user was null'); } - return new ProjectUserObject(projectUserWithRoles); + return projectUserWithRoles; }; /** @@ -370,7 +409,10 @@ export const getProjectUserObject = async ( * @param {IDBConnection} connection * @return {*} {Promise} */ -export const getProjectUserWithRoles = async function (projectId: number, connection: IDBConnection): Promise { +export const getProjectUserWithRoles = async function ( + projectId: number, + connection: IDBConnection +): Promise { const systemUserId = connection.systemUserId(); if (!systemUserId || !projectId) { @@ -383,7 +425,8 @@ export const getProjectUserWithRoles = async function (projectId: number, connec pp.system_user_id, su.record_end_date, array_remove(array_agg(pr.project_role_id), NULL) AS project_role_ids, - array_remove(array_agg(pr.name), NULL) AS project_role_names + array_remove(array_agg(pr.name), NULL) AS project_role_names, + array_remove(array_agg(pp2.name), NULL) as project_role_permissions FROM project_participation pp LEFT JOIN @@ -394,6 +437,10 @@ export const getProjectUserWithRoles = async function (projectId: number, connec system_user su ON pp.system_user_id = su.system_user_id + LEFT JOIN project_role_permission prp + ON prp.project_role_id = pp.project_role_id + LEFT JOIN project_permission pp2 + ON pp2.project_permission_id = prp.project_permission_id WHERE pp.project_id = ${projectId} AND @@ -403,14 +450,14 @@ export const getProjectUserWithRoles = async function (projectId: number, connec GROUP BY pp.project_id, pp.system_user_id, - su.record_end_date ; + su.record_end_date; `; if (!sqlStatement) { return null; } - const response = await connection.query(sqlStatement.text, sqlStatement.values); + const response = await connection.sql(sqlStatement, ProjectUser); return response.rows[0] || null; }; diff --git a/api/src/services/administrative-activity-service.test.ts b/api/src/services/administrative-activity-service.test.ts index 06d82b36f7..1a7ead543e 100644 --- a/api/src/services/administrative-activity-service.test.ts +++ b/api/src/services/administrative-activity-service.test.ts @@ -76,7 +76,7 @@ describe('AdministrativeActivityService', () => { it('should create an administrative activity representing an access request', async () => { const dbConnection = getMockDBConnection(); - const mockRepoResponse = { id: 1, date: new Date() }; + const mockRepoResponse = { id: 1, date: '2023-08-03' }; const repoStub = sinon .stub(AdministrativeActivityRepository.prototype, 'createPendingAccessRequest') .resolves(mockRepoResponse); diff --git a/api/src/services/permit-service.test.ts b/api/src/services/permit-service.test.ts index cc907e9f21..b2c53a8d4e 100644 --- a/api/src/services/permit-service.test.ts +++ b/api/src/services/permit-service.test.ts @@ -2,7 +2,7 @@ import chai, { expect } from 'chai'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { SYSTEM_ROLE } from '../constants/roles'; -import { UserObject } from '../models/user'; +import { User } from '../models/user'; import { IPermitModel, PermitRepository } from '../repositories/permit-repository'; import { getMockDBConnection } from '../__mocks__/db'; import { PermitService } from './permit-service'; @@ -39,8 +39,8 @@ describe('PermitService', () => { } ]; - const mockUserObject: UserObject = { - id: 1, + const mockUserObject: User = { + system_user_id: 1, user_identifier: 'test_user', user_guid: 'aaaa', identity_source: 'idir', @@ -56,10 +56,10 @@ describe('PermitService', () => { const getUserByIdStub = sinon.stub(UserService.prototype, 'getUserById').resolves(mockUserObject); - const response = await permitService.getPermitByUser(mockUserObject.id); + const response = await permitService.getPermitByUser(mockUserObject.system_user_id); expect(getAllPermits).to.be.calledOnce; - expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.id); + expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.system_user_id); expect(response).to.eql(mockPermitResponse); }); @@ -78,8 +78,8 @@ describe('PermitService', () => { } ]; - const mockUserObject: UserObject = { - id: 1, + const mockUserObject: User = { + system_user_id: 1, user_identifier: 'test_user', user_guid: 'aaaa', identity_source: 'idir', @@ -95,10 +95,10 @@ describe('PermitService', () => { const getUserByIdStub = sinon.stub(UserService.prototype, 'getUserById').resolves(mockUserObject); - const response = await permitService.getPermitByUser(mockUserObject.id); + const response = await permitService.getPermitByUser(mockUserObject.system_user_id); expect(getAllPermits).to.be.calledOnce; - expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.id); + expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.system_user_id); expect(response).to.eql(mockPermitResponse); }); @@ -117,8 +117,8 @@ describe('PermitService', () => { } ]; - const mockUserObject: UserObject = { - id: 1, + const mockUserObject: User = { + system_user_id: 1, user_identifier: 'test_user', user_guid: 'aaaa', identity_source: 'idir', @@ -134,10 +134,10 @@ describe('PermitService', () => { const getUserByIdStub = sinon.stub(UserService.prototype, 'getUserById').resolves(mockUserObject); - const response = await permitService.getPermitByUser(mockUserObject.id); + const response = await permitService.getPermitByUser(mockUserObject.system_user_id); expect(getPermitByUser).to.be.calledOnce; - expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.id); + expect(getUserByIdStub).to.be.calledOnceWith(mockUserObject.system_user_id); expect(response).to.eql(mockPermitResponse); }); }); diff --git a/api/src/services/project-participation-service.test.ts b/api/src/services/project-participation-service.test.ts index 17a25f2966..40fb3505c4 100644 --- a/api/src/services/project-participation-service.test.ts +++ b/api/src/services/project-participation-service.test.ts @@ -2,7 +2,7 @@ import chai, { expect } from 'chai'; import { describe } from 'mocha'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import { ProjectUserObject } from '../models/user'; +import { ProjectUser } from '../models/user'; import { ProjectParticipationRepository } from '../repositories/project-participation-repository'; import { getMockDBConnection } from '../__mocks__/db'; import { ProjectParticipationService } from './project-participation-service'; @@ -46,7 +46,7 @@ describe('ProjectParticipationService', () => { const repoStub = sinon .stub(ProjectParticipationRepository.prototype, 'getProjectParticipant') - .resolves(({ project_id: 1, system_user_id: 2 } as unknown) as ProjectUserObject); + .resolves(({ project_id: 1, system_user_id: 2 } as unknown) as ProjectUser); const response = await service.getProjectParticipant(data.projectId, data.systemUserId); @@ -66,7 +66,7 @@ describe('ProjectParticipationService', () => { const repoStub = sinon .stub(ProjectParticipationRepository.prototype, 'getProjectParticipants') - .resolves([{ project_id: 1, system_user_id: 2 } as ProjectUserObject]); + .resolves([{ project_id: 1, system_user_id: 2 } as ProjectUser]); const response = await service.getProjectParticipants(data.projectId); @@ -86,7 +86,7 @@ describe('ProjectParticipationService', () => { const repoStub = sinon .stub(ProjectParticipationRepository.prototype, 'getProjectParticipants') - .resolves([{ project_id: 1, system_user_id: 2 } as ProjectUserObject]); + .resolves([{ project_id: 1, system_user_id: 2 } as ProjectUser]); const response = await service.getProjectParticipants(data.projectId); diff --git a/api/src/services/project-participation-service.ts b/api/src/services/project-participation-service.ts index 5fad69c098..3185707491 100644 --- a/api/src/services/project-participation-service.ts +++ b/api/src/services/project-participation-service.ts @@ -1,5 +1,5 @@ import { IDBConnection } from '../database/db'; -import { ProjectUserObject } from '../models/user'; +import { ProjectUser } from '../models/user'; import { ProjectParticipationRepository } from '../repositories/project-participation-repository'; import { DBService } from './db-service'; @@ -16,7 +16,7 @@ export class ProjectParticipationService extends DBService { return this.projectParticipationRepository.deleteProjectParticipationRecord(projectParticipationId); } - async getProjectParticipant(projectId: number, systemUserId: number): Promise { + async getProjectParticipant(projectId: number, systemUserId: number): Promise { return this.projectParticipationRepository.getProjectParticipant(projectId, systemUserId); } diff --git a/api/src/services/project-service.test.ts b/api/src/services/project-service.test.ts index 5bbf964695..a3d95d8ae8 100644 --- a/api/src/services/project-service.test.ts +++ b/api/src/services/project-service.test.ts @@ -12,7 +12,7 @@ import { GetPartnershipsData, ProjectData } from '../models/project-view'; -import { ProjectUserObject } from '../models/user'; +import { ProjectUser } from '../models/user'; import { IUpdateProject } from '../paths/project/{projectId}/update'; import { ProjectParticipationRepository } from '../repositories/project-participation-repository'; import { ProjectRepository } from '../repositories/project-repository'; @@ -39,7 +39,7 @@ describe('ProjectService', () => { const getProjectParticipantStub = sinon .stub(ProjectService.prototype, 'getProjectParticipant') - .resolves({} as ProjectUserObject); + .resolves({} as ProjectUser); const addProjectParticipantStub = sinon.stub(ProjectService.prototype, 'addProjectParticipant'); @@ -88,7 +88,7 @@ describe('ProjectService', () => { const dbConnection = getMockDBConnection(); const service = new ProjectService(dbConnection); - const data = { project_id: 1 } as ProjectUserObject; + const data = { project_id: 1 } as ProjectUser; const repoStub = sinon.stub(ProjectParticipationRepository.prototype, 'getProjectParticipant').resolves(data); @@ -185,18 +185,26 @@ describe('ProjectService', () => { const data = [ { - id: 123, - name: 'Project 1', + project_id: 123, + uuid: '', + project_name: 'Project 1', + coordinator_agency: '', + project_programs: [], + project_types: [], + regions: [], start_date: '1900-01-01', - end_date: '2200-10-10', - coordinator_agency: 'Agency 1' + end_date: '2200-10-10' }, { - id: 456, - name: 'Project 2', + project_id: 456, + uuid: '', + project_name: 'Project 2', + coordinator_agency: '', + project_programs: [], + project_types: [], + regions: [], start_date: '1900-01-01', - end_date: '2000-12-31', - coordinator_agency: 'Agency 2' + end_date: '2000-12-31' } ]; @@ -223,9 +231,9 @@ describe('ProjectService', () => { const mockProjectMetadataPublish = { project_metadata_publish_id: 1, project_id: 1, - event_timestamp: new Date(), + event_timestamp: '', queue_id: 1, - create_date: new Date(), + create_date: '', create_user: 1, update_date: null, update_user: null, diff --git a/api/src/services/project-service.ts b/api/src/services/project-service.ts index 3490069260..198b550ddc 100644 --- a/api/src/services/project-service.ts +++ b/api/src/services/project-service.ts @@ -29,7 +29,7 @@ import { ProjectData, ProjectSupplementaryData } from '../models/project-view'; -import { ProjectUserObject } from '../models/user'; +import { ProjectUser } from '../models/user'; import { GET_ENTITIES, IUpdateProject } from '../paths/project/{projectId}/update'; import { PublishStatus } from '../repositories/history-publish-repository'; import { ProjectRepository } from '../repositories/project-repository'; @@ -95,7 +95,7 @@ export class ProjectService extends DBService { * @return {*} {Promise} * @memberof ProjectService */ - async getProjectParticipant(projectId: number, systemUserId: number): Promise { + async getProjectParticipant(projectId: number, systemUserId: number): Promise { return this.projectParticipationService.getProjectParticipant(projectId, systemUserId); } @@ -137,11 +137,11 @@ export class ProjectService extends DBService { const response = await this.projectRepository.getProjectList(isUserAdmin, systemUserId, filterFields); return response.map((row) => ({ - id: row.id, - name: row.name, + id: row.project_id, + name: row.project_name, start_date: row.start_date, end_date: row.end_date, - coordinator_agency: row.coordinator_agency_name, + coordinator_agency: row.coordinator_agency, completion_status: (row.end_date && moment(row.end_date).endOf('day').isBefore(moment()) && COMPLETION_STATUS.COMPLETED) || COMPLETION_STATUS.ACTIVE, @@ -395,8 +395,8 @@ export class ProjectService extends DBService { await Promise.all(promises); - // The user that creates a project is automatically assigned a project lead role, for this project - await this.insertParticipantRole(projectId, PROJECT_ROLE.PROJECT_LEAD); + // The user that creates a project is automatically assigned a coordinator role, for this project + await this.insertParticipantRole(projectId, PROJECT_ROLE.COORDINATOR); return projectId; } diff --git a/api/src/services/user-service.test.ts b/api/src/services/user-service.test.ts index a5f08ada38..1b994069be 100644 --- a/api/src/services/user-service.test.ts +++ b/api/src/services/user-service.test.ts @@ -4,8 +4,8 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { SYSTEM_IDENTITY_SOURCE } from '../constants/database'; import { ApiError } from '../errors/api-error'; -import { UserObject } from '../models/user'; -import { IGetUser, IInsertUser, UserRepository } from '../repositories/user-repository'; +import { User } from '../models/user'; +import { UserRepository } from '../repositories/user-repository'; import { getMockDBConnection } from '../__mocks__/db'; import { UserService } from './user-service'; @@ -22,13 +22,13 @@ describe('UserService', () => { const mockResponseRow = { system_user_id: 123 }; const mockUserRepository = sinon.stub(UserRepository.prototype, 'getUserById'); - mockUserRepository.resolves((mockResponseRow as unknown) as IGetUser); + mockUserRepository.resolves((mockResponseRow as unknown) as User); const userService = new UserService(mockDBConnection); const result = await userService.getUserById(1); - expect(result).to.eql(new UserObject(mockResponseRow)); + expect(result).to.eql(mockResponseRow); expect(mockUserRepository).to.have.been.calledOnce; }); }); @@ -56,13 +56,13 @@ describe('UserService', () => { const mockResponseRow = [{ system_user_id: 123 }]; const mockUserRepository = sinon.stub(UserRepository.prototype, 'getUserByGuid'); - mockUserRepository.resolves((mockResponseRow as unknown) as IGetUser[]); + mockUserRepository.resolves((mockResponseRow as unknown) as User[]); const userService = new UserService(mockDBConnection); const result = await userService.getUserByGuid('aaaa'); - expect(result).to.eql(new UserObject(mockResponseRow[0])); + expect(result).to.eql(mockResponseRow[0]); expect(mockUserRepository).to.have.been.calledOnce; }); }); @@ -90,13 +90,13 @@ describe('UserService', () => { const mockResponseRow = [{ system_user_id: 123 }]; const mockUserRepository = sinon.stub(UserRepository.prototype, 'getUserByIdentifier'); - mockUserRepository.resolves((mockResponseRow as unknown) as IGetUser[]); + mockUserRepository.resolves((mockResponseRow as unknown) as User[]); const userService = new UserService(mockDBConnection); const result = await userService.getUserByIdentifier('aaaa', 'bbbb'); - expect(result).to.eql(new UserObject(mockResponseRow[0])); + expect(result).to.eql(mockResponseRow[0]); expect(mockUserRepository).to.have.been.calledOnce; }); }); @@ -111,7 +111,7 @@ describe('UserService', () => { const mockRowObj = { system_user_id: 123 }; const mockUserRepository = sinon.stub(UserRepository.prototype, 'addSystemUser'); - mockUserRepository.resolves((mockRowObj as unknown) as IInsertUser); + mockUserRepository.resolves((mockRowObj as unknown) as User); const userService = new UserService(mockDBConnection); @@ -121,7 +121,7 @@ describe('UserService', () => { const result = await userService.addSystemUser(userGuid, userIdentifier, identitySource); - expect(result).to.eql(new UserObject(mockRowObj)); + expect(result).to.eql(mockRowObj); expect(mockUserRepository).to.have.been.calledOnce; }); }); @@ -148,17 +148,13 @@ describe('UserService', () => { const mockResponseRows = [{ system_user_id: 123 }, { system_user_id: 456 }, { system_user_id: 789 }]; const mockUserRepository = sinon.stub(UserRepository.prototype, 'listSystemUsers'); - mockUserRepository.resolves(mockResponseRows as IGetUser[]); + mockUserRepository.resolves(mockResponseRows as User[]); const userService = new UserService(mockDBConnection); const result = await userService.listSystemUsers(); - expect(result).to.eql([ - new UserObject(mockResponseRows[0]), - new UserObject(mockResponseRows[1]), - new UserObject(mockResponseRows[2]) - ]); + expect(result).to.eql([mockResponseRows[0], mockResponseRows[1], mockResponseRows[2]]); }); }); @@ -200,11 +196,13 @@ describe('UserService', () => { const existingSystemUser = null; const getUserByGuidStub = sinon.stub(UserService.prototype, 'getUserByGuid').resolves(existingSystemUser); - const addedSystemUser = new UserObject({ system_user_id: 2, record_end_date: null }); + const addedSystemUser = ({ system_user_id: 2, record_end_date: null } as unknown) as User; const addSystemUserStub = sinon.stub(UserService.prototype, 'addSystemUser').resolves(addedSystemUser); const activateSystemUserStub = sinon.stub(UserService.prototype, 'activateSystemUser'); + const getUserById = sinon.stub(UserService.prototype, 'getUserById').resolves(addedSystemUser); + const userIdentifier = 'username'; const userGuid = 'aaaa'; const identitySource = SYSTEM_IDENTITY_SOURCE.IDIR; @@ -213,24 +211,28 @@ describe('UserService', () => { const result = await userService.ensureSystemUser(userGuid, userIdentifier, identitySource); - expect(result.id).to.equal(2); + expect(result.system_user_id).to.equal(2); expect(result.record_end_date).to.equal(null); expect(getUserByGuidStub).to.have.been.calledOnce; expect(addSystemUserStub).to.have.been.calledOnce; + expect(getUserById).to.have.been.calledOnce; expect(activateSystemUserStub).not.to.have.been.called; }); it('gets an existing system user that is already activate', async () => { const mockDBConnection = getMockDBConnection({ systemUserId: () => 1 }); - const existingInactiveSystemUser = new UserObject({ + const existingInactiveSystemUser: User = { system_user_id: 2, user_identifier: SYSTEM_IDENTITY_SOURCE.IDIR, + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '', record_end_date: null, role_ids: [1], - role_names: ['Editor'] - }); + role_names: ['Collaborator'] + }; + const getUserByGuidStub = sinon.stub(UserService.prototype, 'getUserByGuid').resolves(existingInactiveSystemUser); const addSystemUserStub = sinon.stub(UserService.prototype, 'addSystemUser'); @@ -245,7 +247,7 @@ describe('UserService', () => { const result = await userService.ensureSystemUser(userGuid, userIdentifier, identitySource); - expect(result.id).to.equal(2); + expect(result.system_user_id).to.equal(2); expect(result.record_end_date).to.equal(null); expect(getUserByGuidStub).to.have.been.calledOnce; @@ -256,26 +258,32 @@ describe('UserService', () => { it('gets an existing system user that is not already active and re-activates it', async () => { const mockDBConnection = getMockDBConnection({ systemUserId: () => 1 }); - const existingSystemUser = new UserObject({ + const existingSystemUser: User = { system_user_id: 2, user_identifier: SYSTEM_IDENTITY_SOURCE.IDIR, - record_end_date: '2021-11-22', + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '', + record_end_date: '1900-01-01', role_ids: [1], - role_names: ['Editor'] - }); + role_names: ['Collaborator'] + }; + const getUserByGuidStub = sinon.stub(UserService.prototype, 'getUserByGuid').resolves(existingSystemUser); const addSystemUserStub = sinon.stub(UserService.prototype, 'addSystemUser'); const activateSystemUserStub = sinon.stub(UserService.prototype, 'activateSystemUser'); - const activatedSystemUser = new UserObject({ + const activatedSystemUser: User = { system_user_id: 2, user_identifier: SYSTEM_IDENTITY_SOURCE.IDIR, + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '', record_end_date: null, role_ids: [1], - role_names: ['Editor'] - }); + role_names: ['Collaborator'] + }; + const getUserByIdStub = sinon.stub(UserService.prototype, 'getUserById').resolves(activatedSystemUser); const userIdentifier = 'username'; @@ -286,7 +294,7 @@ describe('UserService', () => { const result = await userService.ensureSystemUser(userGuid, userIdentifier, identitySource); - expect(result.id).to.equal(2); + expect(result.system_user_id).to.equal(2); expect(result.record_end_date).to.equal(null); expect(getUserByGuidStub).to.have.been.calledOnce; diff --git a/api/src/services/user-service.ts b/api/src/services/user-service.ts index 3009f2fc88..089fff2959 100644 --- a/api/src/services/user-service.ts +++ b/api/src/services/user-service.ts @@ -1,6 +1,6 @@ import { IDBConnection } from '../database/db'; import { ApiBuildSQLError, ApiExecuteSQLError } from '../errors/api-error'; -import { UserObject } from '../models/user'; +import { User } from '../models/user'; import { queries } from '../queries/queries'; import { UserRepository } from '../repositories/user-repository'; import { getLogger } from '../utils/logger'; @@ -32,23 +32,23 @@ export class UserService extends DBService { * Fetch a single system user by their system user ID. * * @param {number} systemUserId - * @return {*} {(Promise)} + * @return {*} {(Promise)} * @memberof UserService */ - async getUserById(systemUserId: number): Promise { + async getUserById(systemUserId: number): Promise { const response = await this.userRepository.getUserById(systemUserId); - return new UserObject(response); + return response; } /** * Get an existing system user by their GUID. * * @param {string} userGuid The user's GUID - * @return {*} {(Promise)} + * @return {*} {(Promise)} * @memberof UserService */ - async getUserByGuid(userGuid: string): Promise { + async getUserByGuid(userGuid: string): Promise { defaultLog.debug({ label: 'getUserByGuid', userGuid }); const response = await this.userRepository.getUserByGuid(userGuid); @@ -57,7 +57,7 @@ export class UserService extends DBService { return null; } - return new UserObject(response[0]); + return response[0]; } /** @@ -65,10 +65,10 @@ export class UserService extends DBService { * * @param userIdentifier the user's identifier * @param identitySource the user's identity source, e.g. `'IDIR'` - * @return {*} {(Promise)} Promise resolving the UserObject, or `null` if the user wasn't found. + * @return {*} {(Promise)} Promise resolving the User, or `null` if the user wasn't found. * @memberof UserService */ - async getUserByIdentifier(userIdentifier: string, identitySource: string): Promise { + async getUserByIdentifier(userIdentifier: string, identitySource: string): Promise { defaultLog.debug({ label: 'getUserByIdentifier', userIdentifier, identitySource }); const response = await this.userRepository.getUserByIdentifier(userIdentifier, identitySource); @@ -77,7 +77,7 @@ export class UserService extends DBService { return null; } - return new UserObject(response[0]); + return response[0]; } /** @@ -88,25 +88,29 @@ export class UserService extends DBService { * @param {string | null} userGuid * @param {string} userIdentifier * @param {string} identitySource - * @return {*} {Promise} + * @return {*} {Promise} * @memberof UserService */ - async addSystemUser(userGuid: string | null, userIdentifier: string, identitySource: string): Promise { + async addSystemUser( + userGuid: string | null, + userIdentifier: string, + identitySource: string + ): Promise<{ system_user_id: number }> { const response = await this.userRepository.addSystemUser(userGuid, userIdentifier, identitySource); - return new UserObject(response); + return response; } /** * Get a list of all system users. * - * @return {*} {Promise} + * @return {*} {Promise} * @memberof UserService */ - async listSystemUsers(): Promise { + async listSystemUsers(): Promise { const response = await this.userRepository.listSystemUsers(); - return response.map((row) => new UserObject(row)); + return response; } /** @@ -116,10 +120,10 @@ export class UserService extends DBService { * @param {string | null} userGuid * @param {string} userIdentifier * @param {string} identitySource - * @return {*} {Promise} + * @return {*} {Promise} * @memberof UserService */ - async ensureSystemUser(userGuid: string | null, userIdentifier: string, identitySource: string): Promise { + async ensureSystemUser(userGuid: string | null, userIdentifier: string, identitySource: string): Promise { // Check if the user exists in SIMS let userObject = userGuid ? await this.getUserByGuid(userGuid) @@ -134,7 +138,10 @@ export class UserService extends DBService { } // Found no existing user, add them - userObject = await this.addSystemUser(userGuid, userIdentifier, identitySource); + const newId = await this.addSystemUser(userGuid, userIdentifier, identitySource); + + // fetch the new user object + userObject = await this.getUserById(newId.system_user_id); } if (!userObject.record_end_date) { @@ -143,17 +150,17 @@ export class UserService extends DBService { } // system user is not active, re-activate them - await this.activateSystemUser(userObject.id); + await this.activateSystemUser(userObject.system_user_id); // get the newly activated user - return this.getUserById(userObject.id); + return this.getUserById(userObject.system_user_id); } /** * Activates an existing system user that had been deactivated (soft deleted). * * @param {number} systemUserId - * @return {*} {(Promise)} + * @return {*} {(Promise)} * @memberof UserService */ async activateSystemUser(systemUserId: number) { @@ -174,7 +181,7 @@ export class UserService extends DBService { * Deactivates an existing system user (soft delete). * * @param {number} systemUserId - * @return {*} {(Promise)} + * @return {*} {(Promise)} * @memberof UserService */ async deactivateSystemUser(systemUserId: number) { diff --git a/app/src/components/attachments/list/AttachmentsListItemMenuButton.tsx b/app/src/components/attachments/list/AttachmentsListItemMenuButton.tsx index b57843a0d8..40d3c29c0d 100644 --- a/app/src/components/attachments/list/AttachmentsListItemMenuButton.tsx +++ b/app/src/components/attachments/list/AttachmentsListItemMenuButton.tsx @@ -7,7 +7,7 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import { ProjectRoleGuard, SystemRoleGuard } from 'components/security/Guards'; import { AttachmentType, PublishStatus } from 'constants/attachments'; -import { PROJECT_ROLE, SYSTEM_ROLE } from 'constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from 'constants/roles'; import { useState } from 'react'; //TODO: PRODUCTION_BANDAGE: Remove from `Remove or Resubmit` button. @@ -116,7 +116,8 @@ const AttachmentsListItemMenuButton = (props: IAttachmentsListItemMenuButtonProp - + { props.onRemoveOrResubmit(); diff --git a/app/src/components/security/Guards.tsx b/app/src/components/security/Guards.tsx index de3b0a37ec..249cb46207 100644 --- a/app/src/components/security/Guards.tsx +++ b/app/src/components/security/Guards.tsx @@ -1,4 +1,4 @@ -import { PROJECT_ROLE, SYSTEM_ROLE } from 'constants/roles'; +import { PROJECT_PERMISSION, PROJECT_ROLE, SYSTEM_ROLE } from 'constants/roles'; import { AuthStateContext } from 'contexts/authStateContext'; import { ProjectAuthStateContext } from 'contexts/projectAuthStateContext'; import { PropsWithChildren, ReactElement, useContext } from 'react'; @@ -26,12 +26,12 @@ export interface ISystemRoleGuardProps extends IGuardProps { export interface IProjectRoleGuardProps extends IGuardProps { /** - * An array of valid project roles. The user must have 1 or more matching project roles to pass the guard. + * An array of valid project roles. The user may have 1 or more matching project roles to pass the guard. * * @type {PROJECT_ROLE[]} * @memberof IProjectRoleGuardProps */ - validProjectRoles: PROJECT_ROLE[]; + validProjectRoles?: PROJECT_ROLE[]; /** * An array of valid system roles. The user may have 1 or more matching system roles to override the guard. * @@ -39,6 +39,13 @@ export interface IProjectRoleGuardProps extends IGuardProps { * @memberof IProjectRoleGuardProps */ validSystemRoles?: SYSTEM_ROLE[]; + + /** + * An array of valid project permissions. The user must have 1 or more matching permissions to pass the guard + * @type {PROJECT_PERMISSION[]} + * @memberof IProjectRoleGuardProps + */ + validProjectPermissions: PROJECT_PERMISSION[]; } /** @@ -70,13 +77,14 @@ export const SystemRoleGuard = (props: PropsWithChildren) * @return {*} */ export const ProjectRoleGuard = (props: PropsWithChildren) => { - const { validProjectRoles, validSystemRoles } = props; + const { validProjectRoles, validSystemRoles, validProjectPermissions } = props; const projectAuthStateContext = useContext(ProjectAuthStateContext); const hasSystemRole = projectAuthStateContext.hasSystemRole(validSystemRoles); const hasProjectRole = projectAuthStateContext.hasProjectRole(validProjectRoles); + const hasProjectPermissions = projectAuthStateContext.hasProjectPermission(validProjectPermissions); - if (hasSystemRole || hasProjectRole) { + if (hasSystemRole || hasProjectRole || hasProjectPermissions) { return <>{props.children}; } @@ -94,11 +102,13 @@ export const ProjectRoleGuard = (props: PropsWithChildren { - const { validProjectRoles, validSystemRoles } = roles; + const { validProjectRoles, validSystemRoles, validProjectPermissions } = roles; const projectAuthStateContext = useContext(ProjectAuthStateContext); return ( - projectAuthStateContext.hasSystemRole(validSystemRoles) || projectAuthStateContext.hasProjectRole(validProjectRoles) + projectAuthStateContext.hasSystemRole(validSystemRoles) || + projectAuthStateContext.hasProjectRole(validProjectRoles) || + projectAuthStateContext.hasProjectPermission(validProjectPermissions) ); }; diff --git a/app/src/components/security/RouteGuards.tsx b/app/src/components/security/RouteGuards.tsx index 712adc237b..d6990b1848 100644 --- a/app/src/components/security/RouteGuards.tsx +++ b/app/src/components/security/RouteGuards.tsx @@ -25,6 +25,16 @@ export interface IProjectRoleRouteGuardProps extends RouteProps { * @type {string[]} */ validProjectRoles?: string[]; + + /** + * Indicates the sufficient project permissions needed to access this route, if any. + * + * Note: The user only needs 1 of the valid roles, when multiple are specified. + * + * @type {string[]} + */ + validProjectPermissions?: string[]; + /** * Indicates the sufficient system roles that will grant access to this route, if any. * @@ -64,11 +74,11 @@ export const SystemRoleRouteGuard = (props: ISystemRoleRouteGuardProps) => { * @return {*} */ export const ProjectRoleRouteGuard = (props: IProjectRoleRouteGuardProps) => { - const { validSystemRoles, validProjectRoles, children, ...rest } = props; + const { validSystemRoles, validProjectRoles, validProjectPermissions, children, ...rest } = props; return ( - + {children} @@ -264,10 +274,14 @@ const CheckIfUserHasSystemRole = (props: ISystemRoleRouteGuardProps) => { * @return {*} */ const CheckIfUserHasProjectRole = (props: IProjectRoleRouteGuardProps) => { - const { validProjectRoles, validSystemRoles, children } = props; - const { hasProjectRole, hasSystemRole } = useContext(ProjectAuthStateContext); - - if (hasProjectRole(validProjectRoles) || hasSystemRole(validSystemRoles)) { + const { validProjectRoles, validSystemRoles, validProjectPermissions, children } = props; + const { hasProjectRole, hasSystemRole, hasProjectPermission } = useContext(ProjectAuthStateContext); + + if ( + hasProjectRole(validProjectRoles) || + hasSystemRole(validSystemRoles) || + hasProjectPermission(validProjectPermissions) + ) { return <>{children}; } diff --git a/app/src/constants/i18n.ts b/app/src/constants/i18n.ts index 9aadf3efe0..909955ef52 100644 --- a/app/src/constants/i18n.ts +++ b/app/src/constants/i18n.ts @@ -261,12 +261,12 @@ export const ProjectParticipantsI18N = { }; export const SystemUserI18N = { - deleteProjectLeadErrorTitle: 'Error Deleting Project Lead', + deleteProjectLeadErrorTitle: 'Error Deleting Coordinator', deleteProjectLeadErrorText: - 'An error has occurred while attempting to delete the project lead, please assign a different project lead before removing. Please try again, if the error persists please contact your system administrator.', - updateProjectLeadRoleErrorTitle: 'Error Updating Project Lead Role', + 'An error has occurred while attempting to delete the coordinator, please assign a different coordinator before removing. Please try again, if the error persists please contact your system administrator.', + updateProjectLeadRoleErrorTitle: 'Error Updating Coordinator Role', updateProjectLeadRoleErrorText: - "An error has occurred while attempting to update the user's project lead role, please assign a different project lead before changing. Please try again, if the error persists please contact your system administrator.", + "An error has occurred while attempting to update the user's coordinator role, please assign a different coordinator before changing. Please try again, if the error persists please contact your system administrator.", removeSystemUserTitle: 'Remove system user?', removeUserFromProject: 'Remove user from project?', removeUserErrorTitle: 'Error Removing User From Team', diff --git a/app/src/constants/roles.ts b/app/src/constants/roles.ts index ec5a920be5..74a14101a7 100644 --- a/app/src/constants/roles.ts +++ b/app/src/constants/roles.ts @@ -16,10 +16,22 @@ export const getAllSystemRoles = () => Object.values(SYSTEM_ROLE); * Project level roles. * * @export - * @enum {number} + * @enum {string} */ export enum PROJECT_ROLE { - PROJECT_LEAD = 'Project Lead', - PROJECT_EDITOR = 'Editor', - PROJECT_VIEWER = 'Viewer' + COORDINATOR = 'Coordinator', + COLLABORATOR = 'Collaborator', + OBSERVER = 'Observer' +} + +/** + * Role permissions. + * + * @export + * @enum {string} + */ +export enum PROJECT_PERMISSION { + COORDINATOR = 'Coordinator', + COLLABORATOR = 'Collaborator', + OBSERVER = 'Observer' } diff --git a/app/src/contexts/projectAuthStateContext.tsx b/app/src/contexts/projectAuthStateContext.tsx index 0a018dfdf5..2f8a0f5693 100644 --- a/app/src/contexts/projectAuthStateContext.tsx +++ b/app/src/contexts/projectAuthStateContext.tsx @@ -9,6 +9,7 @@ export interface IProjectAuthStateContext { getProjectParticipant: () => IGetUserProjectParticipantResponse['participant']; hasProjectRole: (validProjectRoles?: string[]) => boolean; hasSystemRole: (validSystemRoles?: string[]) => boolean; + hasProjectPermission: (validProjectPermissions?: string[]) => boolean; getProjectId: () => number; hasLoadedParticipantInfo: boolean; } @@ -17,6 +18,7 @@ export const ProjectAuthStateContext = React.createContext null, hasProjectRole: () => false, hasSystemRole: () => false, + hasProjectPermission: () => false, getProjectId: () => -1, hasLoadedParticipantInfo: false }); @@ -60,6 +62,27 @@ export const ProjectAuthStateContextProvider: React.FC [getProjectId, getProjectParticipant] ); + const hasProjectPermission = useCallback( + (validProjectPermissions?: string[]): boolean => { + //If no Project role is provided then return false + if (!validProjectPermissions?.length) { + return false; + } + + const participant = getProjectParticipant(); + + if (!participant) { + return false; + } + + return ( + participant?.project_id === getProjectId() && + participant?.project_role_permissions.some((roleName) => validProjectPermissions.includes(roleName)) + ); + }, + [getProjectId, getProjectParticipant] + ); + const hasSystemRole = useCallback( (validSystemRoles?: string[]): boolean => { //If no System role is provided then return false @@ -87,11 +110,19 @@ export const ProjectAuthStateContextProvider: React.FC () => ({ hasProjectRole, hasSystemRole, + hasProjectPermission, getProjectParticipant, getProjectId, hasLoadedParticipantInfo: participantDataLoader.isReady }), - [hasProjectRole, hasSystemRole, getProjectParticipant, getProjectId, participantDataLoader.isReady] + [ + hasProjectRole, + hasSystemRole, + hasProjectPermission, + getProjectParticipant, + getProjectId, + participantDataLoader.isReady + ] ); return ( diff --git a/app/src/features/admin/users/ActiveUsersList.test.tsx b/app/src/features/admin/users/ActiveUsersList.test.tsx index 25e940c30f..c23036643a 100644 --- a/app/src/features/admin/users/ActiveUsersList.test.tsx +++ b/app/src/features/admin/users/ActiveUsersList.test.tsx @@ -53,7 +53,7 @@ describe('ActiveUsersList', () => { const { getByText } = renderContainer({ activeUsers: [ { - id: 1, + system_user_id: 1, user_identifier: 'username', user_guid: 'user-guid', user_record_end_date: '2020-10-10', @@ -75,7 +75,7 @@ describe('ActiveUsersList', () => { const { getByTestId } = renderContainer({ activeUsers: [ { - id: 1, + system_user_id: 1, user_identifier: 'username', user_guid: 'user-guid', user_record_end_date: '2020-10-10', diff --git a/app/src/features/admin/users/ActiveUsersList.tsx b/app/src/features/admin/users/ActiveUsersList.tsx index 92251c2f82..41fc625e82 100644 --- a/app/src/features/admin/users/ActiveUsersList.tsx +++ b/app/src/features/admin/users/ActiveUsersList.tsx @@ -86,11 +86,11 @@ const ActiveUsersList: React.FC = (props) => { }; const deActivateSystemUser = async (user: IGetUserResponse) => { - if (!user?.id) { + if (!user?.system_user_id) { return; } try { - await biohubApi.user.deleteSystemUser(user.id); + await biohubApi.user.deleteSystemUser(user.system_user_id); showSnackBar({ snackbarMessage: ( @@ -149,13 +149,13 @@ const ActiveUsersList: React.FC = (props) => { }; const changeSystemUserRole = async (user: IGetUserResponse, roleId: number, roleName: string) => { - if (!user?.id) { + if (!user?.system_user_id) { return; } const roleIds = [roleId]; try { - await biohubApi.user.updateSystemUserRoles(user.id, roleIds); + await biohubApi.user.updateSystemUserRoles(user.system_user_id, roleIds); showSnackBar({ snackbarMessage: ( @@ -277,9 +277,9 @@ const ActiveUsersList: React.FC = (props) => { )} {activeUsers.length > 0 && activeUsers.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, index) => ( - + - + {row.user_identifier || 'No identifier'} @@ -314,7 +314,7 @@ const ActiveUsersList: React.FC = (props) => { menuLabel: 'View Users Details', menuOnClick: () => history.push({ - pathname: `/admin/users/${row.id}`, + pathname: `/admin/users/${row.system_user_id}`, state: row }) }, diff --git a/app/src/features/admin/users/UsersDetailHeader.test.tsx b/app/src/features/admin/users/UsersDetailHeader.test.tsx index f05b3ca8d0..4f047e53e1 100644 --- a/app/src/features/admin/users/UsersDetailHeader.test.tsx +++ b/app/src/features/admin/users/UsersDetailHeader.test.tsx @@ -18,7 +18,7 @@ const mockUseApi = { }; const mockUser = { - id: 1, + system_user_id: 1, user_record_end_date: 'ending', user_guid: '123', user_identifier: 'testUser', diff --git a/app/src/features/admin/users/UsersDetailHeader.tsx b/app/src/features/admin/users/UsersDetailHeader.tsx index 1e4472930a..b337c4f101 100644 --- a/app/src/features/admin/users/UsersDetailHeader.tsx +++ b/app/src/features/admin/users/UsersDetailHeader.tsx @@ -78,11 +78,11 @@ const UsersDetailHeader: React.FC = (props) => { ); const deActivateSystemUser = async (user: IGetUserResponse) => { - if (!user?.id) { + if (!user?.system_user_id) { return; } try { - await biohubApi.user.deleteSystemUser(user.id); + await biohubApi.user.deleteSystemUser(user.system_user_id); dialogContext.setSnackbar({ snackbarMessage: ( diff --git a/app/src/features/admin/users/UsersDetailPage.test.tsx b/app/src/features/admin/users/UsersDetailPage.test.tsx index 351091549c..8671854dbd 100644 --- a/app/src/features/admin/users/UsersDetailPage.test.tsx +++ b/app/src/features/admin/users/UsersDetailPage.test.tsx @@ -51,7 +51,7 @@ describe('UsersDetailPage', () => { history.push('/admin/users/1'); mockUseApi.user.getUserById.mockResolvedValue({ - id: 1, + system_user_id: 1, user_identifier: 'LongerUserName', user_record_end_date: 'end', role_names: ['role1', 'role2'], diff --git a/app/src/features/admin/users/UsersDetailProjects.test.tsx b/app/src/features/admin/users/UsersDetailProjects.test.tsx index 1c6a1aeaf0..e403dea58d 100644 --- a/app/src/features/admin/users/UsersDetailProjects.test.tsx +++ b/app/src/features/admin/users/UsersDetailProjects.test.tsx @@ -25,7 +25,7 @@ const mockUseApi = { }; const mockUser = { - id: 1, + system_user_id: 1, user_record_end_date: 'ending', user_identifier: 'testUser', role_names: ['system'], @@ -88,7 +88,7 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], - project_roles: [{ id: 1, name: 'Project Lead' }] + project_roles: [{ id: 1, name: 'Coordinator' }] } as any); mockUseApi.project.getAllUserProjectsForView.mockResolvedValue([ @@ -120,7 +120,7 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], - project_roles: [{ id: 1, name: 'Project Lead' }] + project_roles: [{ id: 1, name: 'Coordinator' }] } as any); mockUseApi.project.getAllUserProjectsForView.mockResolvedValue([ @@ -160,7 +160,7 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], - project_roles: [{ id: 1, name: 'Project Lead' }] + project_roles: [{ id: 1, name: 'Coordinator' }] } as any); mockUseApi.project.getAllUserProjectsForView.mockResolvedValue([ @@ -196,7 +196,7 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], - project_roles: [{ id: 1, name: 'Project Lead' }] + project_roles: [{ id: 1, name: 'Coordinator' }] } as any); mockUseApi.project.getAllUserProjectsForView.mockResolvedValue([ @@ -239,7 +239,7 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], - project_roles: [{ id: 1, name: 'Project Lead' }] + project_roles: [{ id: 1, name: 'Coordinator' }] } as any); mockUseApi.project.removeProjectParticipant.mockResolvedValue(true); @@ -309,9 +309,9 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], project_roles: [ - { id: 1, name: 'Project Lead' }, - { id: 2, name: 'Editor' }, - { id: 3, name: 'Viewer' } + { id: 1, name: 'Coordinator' }, + { id: 2, name: 'Collaborator' }, + { id: 3, name: 'Observer' } ] } as any); @@ -337,12 +337,12 @@ describe('UsersDetailProjects', () => { expect(getAllByText('projectName').length).toEqual(1); }); - fireEvent.click(getByText('Viewer')); + fireEvent.click(getByText('Observer')); await waitFor(() => { - expect(getAllByText('Project Lead').length).toEqual(1); - expect(getAllByText('Editor').length).toEqual(1); - expect(getAllByText('Viewer').length).toEqual(2); + expect(getAllByText('Coordinator').length).toEqual(1); + expect(getAllByText('Collaborator').length).toEqual(1); + expect(getAllByText('Observer').length).toEqual(2); }); }); @@ -352,9 +352,9 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], project_roles: [ - { id: 1, name: 'Project Lead' }, - { id: 2, name: 'Editor' }, - { id: 3, name: 'Viewer' } + { id: 1, name: 'Coordinator' }, + { id: 2, name: 'Collaborator' }, + { id: 3, name: 'Observer' } ] } as any); @@ -382,15 +382,15 @@ describe('UsersDetailProjects', () => { expect(getAllByText('projectName').length).toEqual(1); }); - fireEvent.click(getByText('Viewer')); + fireEvent.click(getByText('Observer')); await waitFor(() => { - expect(getAllByText('Project Lead').length).toEqual(1); - expect(getAllByText('Editor').length).toEqual(1); - expect(getAllByText('Viewer').length).toEqual(2); + expect(getAllByText('Coordinator').length).toEqual(1); + expect(getAllByText('Collaborator').length).toEqual(1); + expect(getAllByText('Observer').length).toEqual(2); }); - fireEvent.click(getByText('Editor')); + fireEvent.click(getByText('Collaborator')); await waitFor(() => { expect(getAllByText('Change project role?').length).toEqual(1); @@ -409,9 +409,9 @@ describe('UsersDetailProjects', () => { mockUseApi.codes.getAllCodeSets.mockResolvedValue({ coordinator_agency: [{ id: 1, name: 'agency 1' }], project_roles: [ - { id: 1, name: 'Project Lead' }, - { id: 2, name: 'Editor' }, - { id: 3, name: 'Viewer' } + { id: 1, name: 'Coordinator' }, + { id: 2, name: 'Collaborator' }, + { id: 3, name: 'Observer' } ] } as any); @@ -441,15 +441,15 @@ describe('UsersDetailProjects', () => { expect(getAllByText('projectName').length).toEqual(1); }); - fireEvent.click(getByText('Viewer')); + fireEvent.click(getByText('Observer')); await waitFor(() => { - expect(getAllByText('Project Lead').length).toEqual(1); - expect(getAllByText('Editor').length).toEqual(1); - expect(getAllByText('Viewer').length).toEqual(2); + expect(getAllByText('Coordinator').length).toEqual(1); + expect(getAllByText('Collaborator').length).toEqual(1); + expect(getAllByText('Observer').length).toEqual(2); }); - fireEvent.click(getByText('Editor')); + fireEvent.click(getByText('Collaborator')); await waitFor(() => { expect(getAllByText('Change project role?').length).toEqual(1); @@ -458,7 +458,7 @@ describe('UsersDetailProjects', () => { fireEvent.click(getByText('Change Role')); await waitFor(() => { - expect(getAllByText('Editor').length).toEqual(1); + expect(getAllByText('Collaborator').length).toEqual(1); }); }); }); diff --git a/app/src/features/admin/users/UsersDetailProjects.tsx b/app/src/features/admin/users/UsersDetailProjects.tsx index 7bfa161fe9..69d8bfff1f 100644 --- a/app/src/features/admin/users/UsersDetailProjects.tsx +++ b/app/src/features/admin/users/UsersDetailProjects.tsx @@ -78,15 +78,15 @@ const UsersDetailProjects: React.FC = (props) => { [biohubApi.project] ); - const refresh = () => handleGetUserProjects(userDetails.id); + const refresh = () => handleGetUserProjects(userDetails.system_user_id); useEffect(() => { if (assignedProjects) { return; } - handleGetUserProjects(userDetails.id); - }, [userDetails.id, assignedProjects, handleGetUserProjects]); + handleGetUserProjects(userDetails.system_user_id); + }, [userDetails.system_user_id, assignedProjects, handleGetUserProjects]); useEffect(() => { const getCodes = async () => { @@ -126,7 +126,7 @@ const UsersDetailProjects: React.FC = (props) => { ) }); - handleGetUserProjects(userDetails.id); + handleGetUserProjects(userDetails.system_user_id); } catch (error) { openErrorDialog({ dialogTitle: SystemUserI18N.removeUserErrorTitle, diff --git a/app/src/features/projects/ProjectsRouter.tsx b/app/src/features/projects/ProjectsRouter.tsx index 18a3eeecf7..9696974e26 100644 --- a/app/src/features/projects/ProjectsRouter.tsx +++ b/app/src/features/projects/ProjectsRouter.tsx @@ -1,5 +1,5 @@ import { ProjectRoleRouteGuard } from 'components/security/RouteGuards'; -import { PROJECT_ROLE, SYSTEM_ROLE } from 'constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from 'constants/roles'; import { ProjectAuthStateContextProvider } from 'contexts/projectAuthStateContext'; import { ProjectContextProvider } from 'contexts/projectContext'; import { SurveyContextProvider } from 'contexts/surveyContext'; @@ -44,10 +44,10 @@ const ProjectsRouter: React.FC = () => { @@ -58,7 +58,7 @@ const ProjectsRouter: React.FC = () => { @@ -68,7 +68,7 @@ const ProjectsRouter: React.FC = () => { @@ -78,10 +78,10 @@ const ProjectsRouter: React.FC = () => { @@ -92,10 +92,10 @@ const ProjectsRouter: React.FC = () => { @@ -108,7 +108,7 @@ const ProjectsRouter: React.FC = () => { diff --git a/app/src/features/projects/components/ProjectDetailsForm.tsx b/app/src/features/projects/components/ProjectDetailsForm.tsx index 115a334a99..27385cd370 100644 --- a/app/src/features/projects/components/ProjectDetailsForm.tsx +++ b/app/src/features/projects/components/ProjectDetailsForm.tsx @@ -38,7 +38,7 @@ export const ProjectDetailsFormYupSchema = yup.object().shape({ .min(1, 'At least 1 Project Program is Required') .required('Project Program is Required'), start_date: yup.string().isValidDateString().required('Start Date is Required'), - end_date: yup.string().isValidDateString().isEndDateSameOrAfterStartDate('start_date') + end_date: yup.string().nullable().isValidDateString().isEndDateSameOrAfterStartDate('start_date') }) }); diff --git a/app/src/features/projects/view/ProjectAttachments.test.tsx b/app/src/features/projects/view/ProjectAttachments.test.tsx index 41cd50035d..825cc58b0c 100644 --- a/app/src/features/projects/view/ProjectAttachments.test.tsx +++ b/app/src/features/projects/view/ProjectAttachments.test.tsx @@ -58,6 +58,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true @@ -105,6 +106,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true @@ -149,6 +151,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true @@ -204,6 +207,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true @@ -276,6 +280,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true @@ -350,6 +355,7 @@ describe('ProjectAttachments', () => { const mockProjectAuthStateContext: IProjectAuthStateContext = { getProjectParticipant: () => null, hasProjectRole: () => true, + hasProjectPermission: () => true, hasSystemRole: () => true, getProjectId: () => 1, hasLoadedParticipantInfo: true diff --git a/app/src/features/projects/view/ProjectAttachments.tsx b/app/src/features/projects/view/ProjectAttachments.tsx index 9f7860f232..63d6153ea2 100644 --- a/app/src/features/projects/view/ProjectAttachments.tsx +++ b/app/src/features/projects/view/ProjectAttachments.tsx @@ -9,7 +9,7 @@ import FileUploadWithMetaDialog from 'components/dialog/attachments/FileUploadWi import { IUploadHandler } from 'components/file-upload/FileUploadItem'; import { ProjectRoleGuard } from 'components/security/Guards'; import { H2MenuToolbar } from 'components/toolbar/ActionToolbars'; -import { PROJECT_ROLE, SYSTEM_ROLE } from 'constants/roles'; +import { PROJECT_PERMISSION, SYSTEM_ROLE } from 'constants/roles'; import { ProjectContext } from 'contexts/projectContext'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IUploadAttachmentResponse } from 'interfaces/useProjectApi.interface'; @@ -101,7 +101,7 @@ const ProjectAttachments = () => { ]} renderButton={(buttonProps) => (