diff --git a/docs/ENTITY_RELATIONSHIP_DIAGRAM.md b/docs/ENTITY_RELATIONSHIP_DIAGRAM.md index 0e25a99a..33d51ace 100644 --- a/docs/ENTITY_RELATIONSHIP_DIAGRAM.md +++ b/docs/ENTITY_RELATIONSHIP_DIAGRAM.md @@ -132,6 +132,9 @@ erDiagram Proposal }o--|| User : "is created by" ProposalVersion }o--|| User : "is created by" BulkUpload }o--|| User : "is created by" + User }o--o{ Changemaker : "is associated with" + User }o--o{ Funder : "is associated with" + User }o--o{ DataProvider : "is associated with" ``` ## Narrative diff --git a/package.json b/package.json index 740a30e7..91526790 100644 --- a/package.json +++ b/package.json @@ -101,5 +101,6 @@ "tinypg": "^7.0.0", "tmp-promise": "^3.0.3", "uuid": "^10.0.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/__tests__/users.int.test.ts b/src/__tests__/users.int.test.ts index 624d2401..169cbf25 100644 --- a/src/__tests__/users.int.test.ts +++ b/src/__tests__/users.int.test.ts @@ -1,13 +1,24 @@ import request from 'supertest'; import { v4 as uuidv4 } from 'uuid'; import { app } from '../app'; -import { createUser, loadSystemUser, loadTableMetrics } from '../database'; +import { + createChangemaker, + createOrUpdateChangemakerRole, + createOrUpdateDataProvider, + createOrUpdateDataProviderRole, + createOrUpdateFunder, + createOrUpdateFunderRole, + createUser, + loadSystemUser, + loadTableMetrics, +} from '../database'; import { expectTimestamp, loadTestUser } from '../test/utils'; import { mockJwt as authHeader, mockJwtWithAdminRole as authHeaderWithAdminRole, } from '../test/mockJwt'; import { keycloakUserIdToString, stringToKeycloakUserId } from '../types'; +import { AccessType } from '../types/AccessType'; const createAdditionalTestUser = async () => createUser({ @@ -33,7 +44,84 @@ describe('/users', () => { .expect(200); expect(response.body).toEqual({ total: userCount, - entries: [testUser], + entries: [ + { + keycloakUserId: testUser.keycloakUserId, + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, + createdAt: expectTimestamp, + }, + ], + }); + }); + + it('returns the roles associated with a user', async () => { + const systemUser = await loadSystemUser(); + const testUser = await loadTestUser(); + const dataProvider = await createOrUpdateDataProvider({ + name: 'Test Provider', + shortCode: 'testProvider', + }); + const funder = await createOrUpdateFunder({ + name: 'Test Funder', + shortCode: 'testFunder', + }); + const changemaker = await createChangemaker({ + name: 'Test Changemaker', + taxId: '12-3456789', + }); + await createOrUpdateDataProviderRole({ + dataProviderShortCode: dataProvider.shortCode, + userKeycloakUserId: testUser.keycloakUserId, + accessType: AccessType.MANAGE, + createdBy: systemUser.keycloakUserId, + }); + await createOrUpdateFunderRole({ + funderShortCode: funder.shortCode, + userKeycloakUserId: testUser.keycloakUserId, + accessType: AccessType.EDIT, + createdBy: systemUser.keycloakUserId, + }); + await createOrUpdateChangemakerRole({ + changemakerId: changemaker.id, + userKeycloakUserId: testUser.keycloakUserId, + accessType: AccessType.VIEW, + createdBy: systemUser.keycloakUserId, + }); + const { count: userCount } = await loadTableMetrics('users'); + + const response = await request(app) + .get('/users') + .set(authHeader) + .expect(200); + expect(response.body).toEqual({ + total: userCount, + entries: [ + { + keycloakUserId: testUser.keycloakUserId, + roles: { + changemaker: { + [changemaker.id]: { + view: true, + }, + }, + dataProvider: { + testProvider: { + manage: true, + }, + }, + funder: { + testFunder: { + edit: true, + }, + }, + }, + createdAt: expectTimestamp, + }, + ], }); }); @@ -99,22 +187,47 @@ describe('/users', () => { entries: [ { keycloakUserId: uuids[14], + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, createdAt: expectTimestamp, }, { keycloakUserId: uuids[13], + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, createdAt: expectTimestamp, }, { keycloakUserId: uuids[12], + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, createdAt: expectTimestamp, }, { keycloakUserId: uuids[11], + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, createdAt: expectTimestamp, }, { keycloakUserId: uuids[10], + roles: { + changemaker: {}, + dataProvider: {}, + funder: {}, + }, createdAt: expectTimestamp, }, ], diff --git a/src/database/initialization/changemaker_role_to_json.sql b/src/database/initialization/changemaker_role_to_json.sql new file mode 100644 index 00000000..6a22034e --- /dev/null +++ b/src/database/initialization/changemaker_role_to_json.sql @@ -0,0 +1,14 @@ +SELECT drop_function('changemaker_role_to_json'); + +CREATE FUNCTION changemaker_role_to_json(changemaker_role changemaker_roles) +RETURNS JSONB AS $$ +BEGIN + RETURN jsonb_build_object( + 'userKeycloakUserId', changemaker_role.user_keycloak_user_id, + 'changemakerId', changemaker_role.changemaker_id, + 'accessType', changemaker_role.access_type, + 'createdBy', changemaker_role.created_by, + 'createdAt', changemaker_role.created_at + ); +END; +$$ LANGUAGE plpgsql; diff --git a/src/database/initialization/data_provider_role_to_json.sql b/src/database/initialization/data_provider_role_to_json.sql new file mode 100644 index 00000000..f060985b --- /dev/null +++ b/src/database/initialization/data_provider_role_to_json.sql @@ -0,0 +1,14 @@ +SELECT drop_function('data_provider_role_to_json'); + +CREATE FUNCTION data_provider_role_to_json(data_provider_role data_provider_roles) +RETURNS JSONB AS $$ +BEGIN + RETURN jsonb_build_object( + 'userKeycloakUserId', data_provider_role.user_keycloak_user_id, + 'dataProviderShortCode', data_provider_role.data_provider_short_code, + 'accessType', data_provider_role.access_type, + 'createdBy', data_provider_role.created_by, + 'createdAt', data_provider_role.created_at + ); +END; +$$ LANGUAGE plpgsql; diff --git a/src/database/initialization/funder_role_to_json.sql b/src/database/initialization/funder_role_to_json.sql new file mode 100644 index 00000000..5cf69415 --- /dev/null +++ b/src/database/initialization/funder_role_to_json.sql @@ -0,0 +1,14 @@ +SELECT drop_function('funder_role_to_json'); + +CREATE FUNCTION funder_role_to_json(funder_role funder_roles) +RETURNS JSONB AS $$ +BEGIN + RETURN jsonb_build_object( + 'userKeycloakUserId', funder_role.user_keycloak_user_id, + 'funderShortCode', funder_role.funder_short_code, + 'accessType', funder_role.access_type, + 'createdBy', funder_role.created_by, + 'createdAt', funder_role.created_at + ); +END; +$$ LANGUAGE plpgsql; diff --git a/src/database/initialization/user_to_json.sql b/src/database/initialization/user_to_json.sql index 23948c44..62aa3019 100644 --- a/src/database/initialization/user_to_json.sql +++ b/src/database/initialization/user_to_json.sql @@ -2,9 +2,57 @@ SELECT drop_function('user_to_json'); CREATE FUNCTION user_to_json("user" users) RETURNS JSONB AS $$ +DECLARE + roles_json JSONB := NULL::JSONB; + changemaker_roles_json JSONB := NULL::JSONB; + funder_roles_json JSONB := NULL::JSONB; + data_provider_roles_json JSONB := NULL::JSONB; BEGIN + changemaker_roles_json := ( + SELECT jsonb_object_agg( + changemaker_role_maps.changemaker_id, changemaker_role_maps.role_map + ) + FROM ( + SELECT changemaker_roles.changemaker_id, jsonb_object_agg(changemaker_roles.access_type, TRUE) AS role_map + FROM changemaker_roles + WHERE changemaker_roles.user_keycloak_user_id = "user".keycloak_user_id + GROUP BY changemaker_roles.changemaker_id + ) AS changemaker_role_maps + ); + + data_provider_roles_json := ( + SELECT jsonb_object_agg( + data_provider_role_maps.data_provider_short_code, data_provider_role_maps.role_map + ) + FROM ( + SELECT data_provider_roles.data_provider_short_code, jsonb_object_agg(data_provider_roles.access_type, TRUE) AS role_map + FROM data_provider_roles + WHERE data_provider_roles.user_keycloak_user_id = "user".keycloak_user_id + GROUP BY data_provider_roles.data_provider_short_code + ) AS data_provider_role_maps + ); + + funder_roles_json := ( + SELECT jsonb_object_agg( + funder_role_maps.funder_short_code, funder_role_maps.role_map + ) + FROM ( + SELECT funder_roles.funder_short_code, jsonb_object_agg(funder_roles.access_type, TRUE) AS role_map + FROM funder_roles + WHERE funder_roles.user_keycloak_user_id = "user".keycloak_user_id + GROUP BY funder_roles.funder_short_code + ) AS funder_role_maps + ); + + roles_json := jsonb_build_object( + 'changemaker', COALESCE(changemaker_roles_json, '{}'), + 'dataProvider', COALESCE(data_provider_roles_json, '{}'), + 'funder', COALESCE(funder_roles_json, '{}') + ); + RETURN jsonb_build_object( 'keycloakUserId', "user".keycloak_user_id, + 'roles', roles_json, 'createdAt', "user".created_at ); END; diff --git a/src/database/migrations/0040-create-changemaker_roles.sql b/src/database/migrations/0040-create-changemaker_roles.sql new file mode 100644 index 00000000..02f6fb74 --- /dev/null +++ b/src/database/migrations/0040-create-changemaker_roles.sql @@ -0,0 +1,17 @@ +CREATE TYPE access_type_t AS ENUM ( + 'manage', + 'edit', + 'view' +); + +CREATE TABLE changemaker_roles ( + user_keycloak_user_id UUID NOT NULL, + changemaker_id INT NOT NULL, + access_type access_type_t NOT NULL, + created_by UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_keycloak_user_id, changemaker_id, access_type), + FOREIGN KEY (created_by) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (user_keycloak_user_id) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (changemaker_id) REFERENCES changemakers(id) ON DELETE CASCADE +); diff --git a/src/database/migrations/0041-create-funder_roles.sql b/src/database/migrations/0041-create-funder_roles.sql new file mode 100644 index 00000000..c4d98e0f --- /dev/null +++ b/src/database/migrations/0041-create-funder_roles.sql @@ -0,0 +1,11 @@ +CREATE TABLE funder_roles ( + user_keycloak_user_id UUID NOT NULL, + funder_short_code short_code_t NOT NULL, + access_type access_type_t NOT NULL, + created_by UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_keycloak_user_id, funder_short_code, access_type), + FOREIGN KEY (created_by) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (user_keycloak_user_id) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (funder_short_code) REFERENCES funders(short_code) ON DELETE CASCADE +); diff --git a/src/database/migrations/0042-create-data_provider_roles.sql b/src/database/migrations/0042-create-data_provider_roles.sql new file mode 100644 index 00000000..b4435030 --- /dev/null +++ b/src/database/migrations/0042-create-data_provider_roles.sql @@ -0,0 +1,11 @@ +CREATE TABLE data_provider_roles ( + user_keycloak_user_id UUID NOT NULL, + data_provider_short_code short_code_t NOT NULL, + access_type access_type_t NOT NULL, + created_by UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_keycloak_user_id, data_provider_short_code, access_type), + FOREIGN KEY (created_by) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (user_keycloak_user_id) REFERENCES users(keycloak_user_id) ON DELETE CASCADE, + FOREIGN KEY (data_provider_short_code) REFERENCES data_providers(short_code) ON DELETE CASCADE +); diff --git a/src/database/operations/changemakerRoles/createOrUpdateChangemakerRole.ts b/src/database/operations/changemakerRoles/createOrUpdateChangemakerRole.ts new file mode 100644 index 00000000..f272af3c --- /dev/null +++ b/src/database/operations/changemakerRoles/createOrUpdateChangemakerRole.ts @@ -0,0 +1,32 @@ +import { db } from '../../db'; +import type { + DataProviderRole, + InternallyWritableChangemakerRole, + JsonResultSet, +} from '../../../types'; + +const createOrUpdateChangemakerRole = async ( + createValues: InternallyWritableChangemakerRole, +): Promise => { + const { userKeycloakUserId, changemakerId, accessType, createdBy } = + createValues; + const result = await db.sql>( + 'changemakerRoles.insertOrUpdateOne', + { + changemakerId, + userKeycloakUserId, + accessType, + createdBy, + }, + ); + + const { object } = result.rows[0] ?? {}; + if (object === undefined) { + throw new Error( + 'The entity creation did not appear to fail, but no data was returned by the operation.', + ); + } + return object; +}; + +export { createOrUpdateChangemakerRole }; diff --git a/src/database/operations/changemakerRoles/index.ts b/src/database/operations/changemakerRoles/index.ts new file mode 100644 index 00000000..0b0b800b --- /dev/null +++ b/src/database/operations/changemakerRoles/index.ts @@ -0,0 +1 @@ +export * from './createOrUpdateChangemakerRole'; diff --git a/src/database/operations/dataProviderRoles/createOrUpdateDataProviderRole.ts b/src/database/operations/dataProviderRoles/createOrUpdateDataProviderRole.ts new file mode 100644 index 00000000..469c382a --- /dev/null +++ b/src/database/operations/dataProviderRoles/createOrUpdateDataProviderRole.ts @@ -0,0 +1,32 @@ +import { db } from '../../db'; +import type { + DataProviderRole, + InternallyWritableDataProviderRole, + JsonResultSet, +} from '../../../types'; + +const createOrUpdateDataProviderRole = async ( + createValues: InternallyWritableDataProviderRole, +): Promise => { + const { userKeycloakUserId, dataProviderShortCode, accessType, createdBy } = + createValues; + const result = await db.sql>( + 'dataProviderRoles.insertOrUpdateOne', + { + dataProviderShortCode, + userKeycloakUserId, + accessType, + createdBy, + }, + ); + + const { object } = result.rows[0] ?? {}; + if (object === undefined) { + throw new Error( + 'The entity creation did not appear to fail, but no data was returned by the operation.', + ); + } + return object; +}; + +export { createOrUpdateDataProviderRole }; diff --git a/src/database/operations/dataProviderRoles/index.ts b/src/database/operations/dataProviderRoles/index.ts new file mode 100644 index 00000000..1f99c508 --- /dev/null +++ b/src/database/operations/dataProviderRoles/index.ts @@ -0,0 +1 @@ +export * from './createOrUpdateDataProviderRole'; diff --git a/src/database/operations/funderRoles/createOrUpdateFunderRole.ts b/src/database/operations/funderRoles/createOrUpdateFunderRole.ts new file mode 100644 index 00000000..d347c659 --- /dev/null +++ b/src/database/operations/funderRoles/createOrUpdateFunderRole.ts @@ -0,0 +1,32 @@ +import { db } from '../../db'; +import type { + FunderRole, + InternallyWritableFunderRole, + JsonResultSet, +} from '../../../types'; + +const createOrUpdateFunderRole = async ( + createValues: InternallyWritableFunderRole, +): Promise => { + const { userKeycloakUserId, funderShortCode, accessType, createdBy } = + createValues; + const result = await db.sql>( + 'funderRoles.insertOrUpdateOne', + { + funderShortCode, + userKeycloakUserId, + accessType, + createdBy, + }, + ); + + const { object } = result.rows[0] ?? {}; + if (object === undefined) { + throw new Error( + 'The entity creation did not appear to fail, but no data was returned by the operation.', + ); + } + return object; +}; + +export { createOrUpdateFunderRole }; diff --git a/src/database/operations/funderRoles/index.ts b/src/database/operations/funderRoles/index.ts new file mode 100644 index 00000000..14bcb0ed --- /dev/null +++ b/src/database/operations/funderRoles/index.ts @@ -0,0 +1 @@ +export * from './createOrUpdateFunderRole'; diff --git a/src/database/operations/index.ts b/src/database/operations/index.ts index f7d0c1cf..d5a679ac 100644 --- a/src/database/operations/index.ts +++ b/src/database/operations/index.ts @@ -4,8 +4,11 @@ export * from './baseFieldLocalization'; export * from './baseFields'; export * from './bulkUploads'; export * from './changemakerProposals'; +export * from './changemakerRoles'; export * from './changemakers'; +export * from './dataProviderRoles'; export * from './dataProviders'; +export * from './funderRoles'; export * from './funders'; export * from './generic'; export * from './opportunities'; diff --git a/src/database/queries/changemakerRoles/insertOrUpdateOne.sql b/src/database/queries/changemakerRoles/insertOrUpdateOne.sql new file mode 100644 index 00000000..47e606bc --- /dev/null +++ b/src/database/queries/changemakerRoles/insertOrUpdateOne.sql @@ -0,0 +1,14 @@ +INSERT INTO changemaker_roles ( + changemaker_id, + user_keycloak_user_id, + access_type, + created_by +) VALUES ( + :changemakerId, + :userKeycloakUserId, + :accessType::access_type_t, + :createdBy +) +ON CONFLICT (changemaker_id, user_keycloak_user_id, access_type) +DO NOTHING +RETURNING changemaker_role_to_json(changemaker_roles) AS "object"; diff --git a/src/database/queries/dataProviderRoles/insertOrUpdateOne.sql b/src/database/queries/dataProviderRoles/insertOrUpdateOne.sql new file mode 100644 index 00000000..7367ea0c --- /dev/null +++ b/src/database/queries/dataProviderRoles/insertOrUpdateOne.sql @@ -0,0 +1,19 @@ +INSERT INTO data_provider_roles ( + data_provider_short_code, + user_keycloak_user_id, + access_type, + created_by +) VALUES ( + :dataProviderShortCode, + :userKeycloakUserId, + :accessType::access_type_t, + :createdBy +) +ON CONFLICT (data_provider_short_code, user_keycloak_user_id, access_type) +DO NOTHING +RETURNING data_provider_role_to_json(data_provider_roles) AS "object"; + +-- DECISIONS: move assigned role to a better name, that reflects granted ability / accessType / etc and make it part of the PK +-- PUT /users/{userKeycloakUserId}/roles/funders/{shortCode}/accessType/administrator => {} +-- GET /users/{userKeycloakUserId}/roles/funders/{shortCode}/accessType/administrator +-- DELETE /users/{userKeycloakUserId}/roles/funders/{shortCode}/accessType/administrator diff --git a/src/database/queries/funderRoles/insertOrUpdateOne.sql b/src/database/queries/funderRoles/insertOrUpdateOne.sql new file mode 100644 index 00000000..3bd381bf --- /dev/null +++ b/src/database/queries/funderRoles/insertOrUpdateOne.sql @@ -0,0 +1,14 @@ +INSERT INTO funder_roles ( + funder_short_code, + user_keycloak_user_id, + access_type, + created_by +) VALUES ( + :funderShortCode, + :userKeycloakUserId, + :accessType::access_type_t, + :createdBy +) +ON CONFLICT (funder_short_code, user_keycloak_user_id, access_type) +DO NOTHING +RETURNING funder_role_to_json(funder_roles) AS "object"; diff --git a/src/openapi.json b/src/openapi.json index d5761457..2c9cb28f 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -947,6 +947,95 @@ } ] }, + "UserRole": { + "type": "string", + "enum": ["administrator", "editor", "viewer"] + }, + "ChangemakerRole": { + "type": "object", + "properties": { + "changemakerId": { + "type": "integer", + "readOnly": true + }, + "userKeycloakUserId": { + "type": "string", + "format": "uuid" + }, + "assignedRole": { + "$ref": "#/components/schemas/UserRole" + }, + "createdBy": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": ["changemakerId", "userKeycloakUserId", "assignedRole"] + }, + "DataProviderRole": { + "type": "object", + "properties": { + "dataProviderShortCode": { + "type": "string", + "readOnly": true + }, + "userKeycloakUserId": { + "type": "string", + "format": "uuid" + }, + "assignedRole": { + "$ref": "#/components/schemas/UserRole" + }, + "createdBy": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "dataProviderShortCode", + "userKeycloakUserId", + "assignedRole" + ] + }, + "FunderRole": { + "type": "object", + "properties": { + "funderShortCode": { + "type": "string", + "readOnly": true + }, + "userKeycloakUserId": { + "type": "string", + "format": "uuid" + }, + "assignedRole": { + "$ref": "#/components/schemas/UserRole" + }, + "createdBy": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": ["funderShortCode", "userKeycloakUserId", "assignedRole"] + }, "User": { "type": "object", "properties": { @@ -954,6 +1043,31 @@ "type": "string", "example": "550e8400-e29b-41d4-a716-446655440000" }, + "roles": { + "type": "object", + "readOnly": true, + "properties": { + "changemaker": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ChangemakerRole" + } + }, + "dataProvider": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/DataProviderRole" + } + }, + "funder": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FunderRole" + } + } + }, + "required": ["changemaker", "dataProvider", "funder"] + }, "createdAt": { "type": "string", "format": "date-time", diff --git a/src/types/AccessType.ts b/src/types/AccessType.ts new file mode 100644 index 00000000..5fad7506 --- /dev/null +++ b/src/types/AccessType.ts @@ -0,0 +1,7 @@ +enum AccessType { + MANAGE = 'manage', + EDIT = 'edit', + VIEW = 'view', +} + +export { AccessType }; diff --git a/src/types/ChangemakerRole.ts b/src/types/ChangemakerRole.ts new file mode 100644 index 00000000..c42a1823 --- /dev/null +++ b/src/types/ChangemakerRole.ts @@ -0,0 +1,36 @@ +import { ajv } from '../ajv'; +import { AccessType } from './AccessType'; +import type { JSONSchemaType } from 'ajv'; +import type { Writable } from './Writable'; +import type { KeycloakUserId } from './KeycloakUserId'; + +interface ChangemakerRole { + readonly changemakerId: number; + readonly userKeycloakUserId: KeycloakUserId; + readonly accessType: AccessType; + readonly createdBy: KeycloakUserId; + readonly createdAt: string; +} + +type WritableChangemakerRole = Writable; + +type InternallyWritableChangemakerRole = WritableChangemakerRole & + Pick< + ChangemakerRole, + 'changemakerId' | 'userKeycloakUserId' | 'accessType' | 'createdBy' + >; + +const writableChangemakerRoleSchema: JSONSchemaType = { + type: 'object', + properties: {}, + required: [], +}; + +const isWritableChangemakerRole = ajv.compile(writableChangemakerRoleSchema); + +export { + ChangemakerRole, + InternallyWritableChangemakerRole, + isWritableChangemakerRole, + WritableChangemakerRole, +}; diff --git a/src/types/DataProviderRole.ts b/src/types/DataProviderRole.ts new file mode 100644 index 00000000..6d2567a5 --- /dev/null +++ b/src/types/DataProviderRole.ts @@ -0,0 +1,37 @@ +import { ajv } from '../ajv'; +import { AccessType } from './AccessType'; +import type { JSONSchemaType } from 'ajv'; +import type { Writable } from './Writable'; +import type { ShortCode } from './ShortCode'; +import type { KeycloakUserId } from './KeycloakUserId'; + +interface DataProviderRole { + readonly dataProviderShortCode: ShortCode; + readonly userKeycloakUserId: KeycloakUserId; + readonly accessType: AccessType; + readonly createdBy: KeycloakUserId; + readonly createdAt: string; +} + +type WritableDataProviderRole = Writable; + +type InternallyWritableDataProviderRole = WritableDataProviderRole & + Pick< + DataProviderRole, + 'dataProviderShortCode' | 'userKeycloakUserId' | 'accessType' | 'createdBy' + >; + +const writableDataProviderSchema: JSONSchemaType = { + type: 'object', + properties: {}, + required: [], +}; + +const isWritableDataProviderRole = ajv.compile(writableDataProviderSchema); + +export { + DataProviderRole, + InternallyWritableDataProviderRole, + isWritableDataProviderRole, + WritableDataProviderRole, +}; diff --git a/src/types/FunderRole.ts b/src/types/FunderRole.ts new file mode 100644 index 00000000..fba12003 --- /dev/null +++ b/src/types/FunderRole.ts @@ -0,0 +1,37 @@ +import { ajv } from '../ajv'; +import { AccessType } from './AccessType'; +import type { JSONSchemaType } from 'ajv'; +import type { Writable } from './Writable'; +import type { ShortCode } from './ShortCode'; +import type { KeycloakUserId } from './KeycloakUserId'; + +interface FunderRole { + readonly funderShortCode: ShortCode; + readonly userKeycloakUserId: KeycloakUserId; + readonly accessType: AccessType; + readonly createdBy: KeycloakUserId; + readonly createdAt: string; +} + +type WritableFunderRole = Writable; + +type InternallyWritableFunderRole = WritableFunderRole & + Pick< + FunderRole, + 'funderShortCode' | 'userKeycloakUserId' | 'accessType' | 'createdBy' + >; + +const writableFunderRoleSchema: JSONSchemaType = { + type: 'object', + properties: {}, + required: [], +}; + +const isWritableFunderRole = ajv.compile(writableFunderRoleSchema); + +export { + FunderRole, + InternallyWritableFunderRole, + isWritableFunderRole, + WritableFunderRole, +}; diff --git a/src/types/index.ts b/src/types/index.ts index 5ca7bc4b..91718b0f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,10 +7,13 @@ export * from './BulkUpload'; export * from './Bundle'; export * from './Changemaker'; export * from './ChangemakerProposal'; +export * from './ChangemakerRole'; export * from './CheckResult'; export * from './DataProvider'; +export * from './DataProviderRole'; export * from './express/AuthenticatedRequest'; export * from './Funder'; +export * from './FunderRole'; export * from './Id'; export * from './JsonObject'; export * from './JsonResultSet';