From a1fe14f881d0c0b1d8e8f6893a04d3d7ee2f3b05 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Tue, 28 Apr 2020 20:39:10 +0100 Subject: [PATCH 1/7] Add share duration to sharing code --- .../service/access-sharing/CreateSharingCode.test.ts | 2 +- .../service/access-sharing/CreateSharingCode.ts | 2 +- src/database/migrations/V4__addSharingCode.ts | 1 + src/domain/model/sharingCode/SharingCode.ts | 7 ++++++- .../persistence/PsqlSharingCodeRepository.ts | 9 +++++---- .../api/access-sharing/SharingCodeController.ts | 6 +++++- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/application/service/access-sharing/CreateSharingCode.test.ts b/src/application/service/access-sharing/CreateSharingCode.test.ts index 74cd7f2..f4cdd80 100644 --- a/src/application/service/access-sharing/CreateSharingCode.test.ts +++ b/src/application/service/access-sharing/CreateSharingCode.test.ts @@ -11,7 +11,7 @@ describe('CreateSharingCode', () => { }); it('creates a new sharing code with a userId', async () => { - const persistedSharingCode: SharingCode = await createSharingCode.withUserId(uuidv4()); + const persistedSharingCode: SharingCode = await createSharingCode.withUserId(uuidv4(), 20); const sharingCode = await sharingCodeRepository.findByCode(persistedSharingCode.code); diff --git a/src/application/service/access-sharing/CreateSharingCode.ts b/src/application/service/access-sharing/CreateSharingCode.ts index 2860530..0b138be 100644 --- a/src/application/service/access-sharing/CreateSharingCode.ts +++ b/src/application/service/access-sharing/CreateSharingCode.ts @@ -5,7 +5,7 @@ import { UserId } from '../../../domain/model/user/UserId'; export class CreateSharingCode { constructor(private sharingCodeRepository: SharingCodeRepository) {} - public async withUserId(userId: string): Promise { + public async withUserId(userId: string, duration: number = 15): Promise { const sharingCode = new SharingCode(new UserId(userId)); return this.sharingCodeRepository.save(sharingCode); } diff --git a/src/database/migrations/V4__addSharingCode.ts b/src/database/migrations/V4__addSharingCode.ts index 532ffd7..e3802fe 100644 --- a/src/database/migrations/V4__addSharingCode.ts +++ b/src/database/migrations/V4__addSharingCode.ts @@ -6,6 +6,7 @@ export async function up(db: knex) { await db.schema.createTable(SHARING_CODE_TABLE, (table) => { table.uuid('code').primary(); table.uuid('user_id').notNullable(); + table.integer('duration').notNullable(); table.timestamp('creation_time').notNullable().index(); }); } diff --git a/src/domain/model/sharingCode/SharingCode.ts b/src/domain/model/sharingCode/SharingCode.ts index 8324ca8..22da036 100644 --- a/src/domain/model/sharingCode/SharingCode.ts +++ b/src/domain/model/sharingCode/SharingCode.ts @@ -5,7 +5,12 @@ import { UserId } from '../user/UserId'; const SHARING_CODE_LIFETIME_MSEC = 5 * 60 * 1_000; export class SharingCode { - constructor(readonly userId: UserId, readonly code: string = uuidv4(), readonly creationTime: Date = new Date()) {} + constructor( + readonly userId: UserId, + readonly duration: number = 15, + readonly code: string = uuidv4(), + readonly creationTime: Date = new Date() + ) {} public isExpired(): boolean { return Date.now() > this.expirationTime().getTime(); diff --git a/src/infrastructure/persistence/PsqlSharingCodeRepository.ts b/src/infrastructure/persistence/PsqlSharingCodeRepository.ts index 48b2b1a..d1837f1 100644 --- a/src/infrastructure/persistence/PsqlSharingCodeRepository.ts +++ b/src/infrastructure/persistence/PsqlSharingCodeRepository.ts @@ -8,26 +8,27 @@ export class PsqlSharingCodeRepository implements SharingCodeRepository { async findByCode(code: string) { const linkRow: any = await this.db('sharing_code') - .select(['code', 'user_id as userId', 'creation_time as creationTime']) + .select(['code', 'user_id as userId', 'duration', 'creation_time as creationTime']) .where('code', '=', code) .first(); if (!linkRow) { return null; } - return new SharingCode(new UserId(linkRow.userId), linkRow.code, linkRow.creationTime); + return new SharingCode(new UserId(linkRow.userId), linkRow.code, linkRow.duration, linkRow.creationTime); } async save(sharingCode: SharingCode) { return await this.db .raw( ` - insert into sharing_code (code, user_id, creation_time) - values (:code, :user_id, :creation_time) + insert into sharing_code (code, user_id, duration, creation_time) + values (:code, :user_id, :duration, :creation_time) `, { code: sharingCode.code, user_id: sharingCode.userId.value, + duration: sharingCode.duration, creation_time: sharingCode.creationTime, } ) diff --git a/src/presentation/api/access-sharing/SharingCodeController.ts b/src/presentation/api/access-sharing/SharingCodeController.ts index 22f3011..d524422 100644 --- a/src/presentation/api/access-sharing/SharingCodeController.ts +++ b/src/presentation/api/access-sharing/SharingCodeController.ts @@ -12,7 +12,11 @@ export class SharingCodeController { private createSharingCode = createSharingCode; @Post('') - async createNewAccessPass(@Param('userId') userIdValue: string, @CurrentUser({ required: true }) actor: User) { + async createNewAccessPass( + @Param('userId') userIdValue: string, + @Param('duration') duration: number, + @CurrentUser({ required: true }) actor: User + ) { await this.ensureIsLoggedInAsUser(actor, new UserId(userIdValue)); const sharingCode = await this.createSharingCode.withUserId(userIdValue); From 1538c84299bb90aa18d480fac5696ced1c354813 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Sat, 2 May 2020 20:36:20 +0100 Subject: [PATCH 2/7] Rename duration, fix migration --- src/database/migrations/V4__addSharingCode.ts | 1 - .../V7__addSharingCodeAccessDuration.ts | 21 +++++++++++++++++++ src/domain/model/sharingCode/SharingCode.ts | 2 +- .../persistence/PsqlSharingCodeRepository.ts | 10 ++++----- .../AccessPassController.test.ts | 4 ++-- .../access-sharing/SharingCodeController.ts | 4 ++-- 6 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 src/database/migrations/V7__addSharingCodeAccessDuration.ts diff --git a/src/database/migrations/V4__addSharingCode.ts b/src/database/migrations/V4__addSharingCode.ts index e3802fe..532ffd7 100644 --- a/src/database/migrations/V4__addSharingCode.ts +++ b/src/database/migrations/V4__addSharingCode.ts @@ -6,7 +6,6 @@ export async function up(db: knex) { await db.schema.createTable(SHARING_CODE_TABLE, (table) => { table.uuid('code').primary(); table.uuid('user_id').notNullable(); - table.integer('duration').notNullable(); table.timestamp('creation_time').notNullable().index(); }); } diff --git a/src/database/migrations/V7__addSharingCodeAccessDuration.ts b/src/database/migrations/V7__addSharingCodeAccessDuration.ts new file mode 100644 index 0000000..3566448 --- /dev/null +++ b/src/database/migrations/V7__addSharingCodeAccessDuration.ts @@ -0,0 +1,21 @@ +import knex from 'knex'; + +const SHARING_CODE_TABLE = 'sharing_code'; + +export async function up(db: knex) { + await db.schema.table(SHARING_CODE_TABLE, (table) => { + table.integer('access_duration'); + }); + + await db('user').where({ access_duration: null }).update({ access_duration: 15 }); + + await db.schema.alterTable('user', (table) => { + table.string('access_duration').notNullable().alter(); + }); +} + +export async function down(db: knex) { + await db.schema.alterTable('user', (table) => { + table.dropColumn('access_duration'); + }); +} diff --git a/src/domain/model/sharingCode/SharingCode.ts b/src/domain/model/sharingCode/SharingCode.ts index 22da036..af51a8f 100644 --- a/src/domain/model/sharingCode/SharingCode.ts +++ b/src/domain/model/sharingCode/SharingCode.ts @@ -7,7 +7,7 @@ const SHARING_CODE_LIFETIME_MSEC = 5 * 60 * 1_000; export class SharingCode { constructor( readonly userId: UserId, - readonly duration: number = 15, + readonly accessDuration: number = 15, // Minutes readonly code: string = uuidv4(), readonly creationTime: Date = new Date() ) {} diff --git a/src/infrastructure/persistence/PsqlSharingCodeRepository.ts b/src/infrastructure/persistence/PsqlSharingCodeRepository.ts index d1837f1..5c9ce04 100644 --- a/src/infrastructure/persistence/PsqlSharingCodeRepository.ts +++ b/src/infrastructure/persistence/PsqlSharingCodeRepository.ts @@ -8,27 +8,27 @@ export class PsqlSharingCodeRepository implements SharingCodeRepository { async findByCode(code: string) { const linkRow: any = await this.db('sharing_code') - .select(['code', 'user_id as userId', 'duration', 'creation_time as creationTime']) + .select(['code', 'user_id as userId', 'access_duration as accessDuration', 'creation_time as creationTime']) .where('code', '=', code) .first(); if (!linkRow) { return null; } - return new SharingCode(new UserId(linkRow.userId), linkRow.code, linkRow.duration, linkRow.creationTime); + return new SharingCode(new UserId(linkRow.userId), linkRow.accessDuration, linkRow.code, linkRow.creationTime); } async save(sharingCode: SharingCode) { return await this.db .raw( ` - insert into sharing_code (code, user_id, duration, creation_time) - values (:code, :user_id, :duration, :creation_time) + insert into sharing_code (code, user_id, access_duration, creation_time) + values (:code, :user_id, :access_duration, :creation_time) `, { code: sharingCode.code, user_id: sharingCode.userId.value, - duration: sharingCode.duration, + access_duration: sharingCode.accessDuration, creation_time: sharingCode.creationTime, } ) diff --git a/src/presentation/api/access-sharing/AccessPassController.test.ts b/src/presentation/api/access-sharing/AccessPassController.test.ts index 8676c83..c5db18f 100644 --- a/src/presentation/api/access-sharing/AccessPassController.test.ts +++ b/src/presentation/api/access-sharing/AccessPassController.test.ts @@ -51,7 +51,7 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, uuidv4(), new Date('1970-01-01')); + const sharingCode = new SharingCode(user2.id, 15, uuidv4(), new Date('1970-01-01')); await sharingCodeRepository.save(sharingCode); await request(app) @@ -68,7 +68,7 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, uuidv4(), new Date()); + const sharingCode = new SharingCode(user2.id, 15, uuidv4(), new Date()); await sharingCodeRepository.save(sharingCode); await request(app) diff --git a/src/presentation/api/access-sharing/SharingCodeController.ts b/src/presentation/api/access-sharing/SharingCodeController.ts index d524422..a98be08 100644 --- a/src/presentation/api/access-sharing/SharingCodeController.ts +++ b/src/presentation/api/access-sharing/SharingCodeController.ts @@ -14,12 +14,12 @@ export class SharingCodeController { @Post('') async createNewAccessPass( @Param('userId') userIdValue: string, - @Param('duration') duration: number, + @Param('accessDuration') accessDuration: number, @CurrentUser({ required: true }) actor: User ) { await this.ensureIsLoggedInAsUser(actor, new UserId(userIdValue)); - const sharingCode = await this.createSharingCode.withUserId(userIdValue); + const sharingCode = await this.createSharingCode.withUserId(userIdValue, accessDuration); return this.sharingCodeTransformer.toSharingCodeDTO(sharingCode); } From 282267a4e4d499dd1fa02cee09b9e592aeaee1c6 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Sat, 2 May 2020 20:36:42 +0100 Subject: [PATCH 3/7] Missed change from last commit --- src/application/service/access-sharing/CreateSharingCode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/application/service/access-sharing/CreateSharingCode.ts b/src/application/service/access-sharing/CreateSharingCode.ts index 0b138be..1fb1b5d 100644 --- a/src/application/service/access-sharing/CreateSharingCode.ts +++ b/src/application/service/access-sharing/CreateSharingCode.ts @@ -5,8 +5,8 @@ import { UserId } from '../../../domain/model/user/UserId'; export class CreateSharingCode { constructor(private sharingCodeRepository: SharingCodeRepository) {} - public async withUserId(userId: string, duration: number = 15): Promise { - const sharingCode = new SharingCode(new UserId(userId)); + public async withUserId(userId: string, accessDuration: number = 15): Promise { + const sharingCode = new SharingCode(new UserId(userId), accessDuration); return this.sharingCodeRepository.save(sharingCode); } } From b1089ec985bd7f4fc4de21b2f41727f4c652aa05 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Mon, 4 May 2020 10:10:21 +0100 Subject: [PATCH 4/7] Fix up accessDuration for sharing codes --- .../migrations/V7__addSharingCodeAccessDuration.ts | 8 ++++---- .../api/access-sharing/SharingCodeController.test.ts | 2 ++ .../api/access-sharing/SharingCodeController.ts | 7 ++++--- src/presentation/dtos/access-sharing/SharingCodeDTO.ts | 1 + src/presentation/transformers/SharingCodeTransformer.ts | 1 + 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/database/migrations/V7__addSharingCodeAccessDuration.ts b/src/database/migrations/V7__addSharingCodeAccessDuration.ts index 3566448..880bcbb 100644 --- a/src/database/migrations/V7__addSharingCodeAccessDuration.ts +++ b/src/database/migrations/V7__addSharingCodeAccessDuration.ts @@ -7,15 +7,15 @@ export async function up(db: knex) { table.integer('access_duration'); }); - await db('user').where({ access_duration: null }).update({ access_duration: 15 }); + await db(SHARING_CODE_TABLE).where({ access_duration: null }).update({ access_duration: 15 }); - await db.schema.alterTable('user', (table) => { - table.string('access_duration').notNullable().alter(); + await db.schema.alterTable(SHARING_CODE_TABLE, (table) => { + table.integer('access_duration').notNullable().alter(); }); } export async function down(db: knex) { - await db.schema.alterTable('user', (table) => { + await db.schema.alterTable(SHARING_CODE_TABLE, (table) => { table.dropColumn('access_duration'); }); } diff --git a/src/presentation/api/access-sharing/SharingCodeController.test.ts b/src/presentation/api/access-sharing/SharingCodeController.test.ts index f8f0861..5cdb730 100644 --- a/src/presentation/api/access-sharing/SharingCodeController.test.ts +++ b/src/presentation/api/access-sharing/SharingCodeController.test.ts @@ -31,10 +31,12 @@ describe('SharingCodeController', () => { await request(app) .post(`/api/v1/users/${id.value}/sharing-code`) .set({ Authorization: `Bearer ${await getTokenForUser(user)}` }) + .send({ accessDuration: 60 }) .expect(200) .expect((response) => { const sharingCode = response.body; expect(sharingCode.code).toBeDefined(); + expect(sharingCode.accessDuration).toBe(60); }); }); diff --git a/src/presentation/api/access-sharing/SharingCodeController.ts b/src/presentation/api/access-sharing/SharingCodeController.ts index a98be08..02b13c9 100644 --- a/src/presentation/api/access-sharing/SharingCodeController.ts +++ b/src/presentation/api/access-sharing/SharingCodeController.ts @@ -1,9 +1,10 @@ import { createSharingCode } from '../../../application/service'; import { ApiError, apiErrorCodes } from '../../dtos/ApiError'; import { UserId } from '../../../domain/model/user/UserId'; -import { Authorized, CurrentUser, JsonController, Param, Post } from 'routing-controllers'; +import { Authorized, CurrentUser, JsonController, Param, BodyParam, Post } from 'routing-controllers'; import { SharingCodeTransformer } from '../../transformers/SharingCodeTransformer'; import { User } from '../../../domain/model/user/User'; +import { SharingCodeDTO } from '../../dtos/access-sharing/SharingCodeDTO'; @Authorized() @JsonController('/v1/users/:userId/sharing-code') @@ -14,9 +15,9 @@ export class SharingCodeController { @Post('') async createNewAccessPass( @Param('userId') userIdValue: string, - @Param('accessDuration') accessDuration: number, + @BodyParam('accessDuration') accessDuration: number, @CurrentUser({ required: true }) actor: User - ) { + ): Promise { await this.ensureIsLoggedInAsUser(actor, new UserId(userIdValue)); const sharingCode = await this.createSharingCode.withUserId(userIdValue, accessDuration); diff --git a/src/presentation/dtos/access-sharing/SharingCodeDTO.ts b/src/presentation/dtos/access-sharing/SharingCodeDTO.ts index f33ba04..7de9a1d 100644 --- a/src/presentation/dtos/access-sharing/SharingCodeDTO.ts +++ b/src/presentation/dtos/access-sharing/SharingCodeDTO.ts @@ -1,4 +1,5 @@ export interface SharingCodeDTO { code: string; + accessDuration: number; expiryTime: Date; } diff --git a/src/presentation/transformers/SharingCodeTransformer.ts b/src/presentation/transformers/SharingCodeTransformer.ts index 6f34217..211c5d1 100644 --- a/src/presentation/transformers/SharingCodeTransformer.ts +++ b/src/presentation/transformers/SharingCodeTransformer.ts @@ -5,6 +5,7 @@ export class SharingCodeTransformer { public toSharingCodeDTO(sharingCode: SharingCode): SharingCodeDTO { return { code: sharingCode.code, + accessDuration: sharingCode.accessDuration, expiryTime: sharingCode.expirationTime(), }; } From c9aad1b9d167e18377739a36f2600a39e48269b0 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Mon, 4 May 2020 11:08:43 +0100 Subject: [PATCH 5/7] Add duration to access pass --- .../access-sharing/CreateAccessPass.ts | 2 +- .../migrations/V8__addDurationToAccessPass.ts | 21 +++++++++++++++++++ .../model/accessPass/AccessPass.test.ts | 10 ++++++--- src/domain/model/accessPass/AccessPass.ts | 7 ++++--- .../PsqlAccessPassRepository.test.ts | 8 +++---- .../persistence/PsqlAccessPassRepository.ts | 7 +++++-- .../AccessPassController.test.ts | 7 +++++-- .../api/tests/TestController.test.ts | 2 +- .../api/users/UserController.test.ts | 2 +- 9 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 src/database/migrations/V8__addDurationToAccessPass.ts diff --git a/src/application/service/access-sharing/CreateAccessPass.ts b/src/application/service/access-sharing/CreateAccessPass.ts index 29128d5..4549518 100644 --- a/src/application/service/access-sharing/CreateAccessPass.ts +++ b/src/application/service/access-sharing/CreateAccessPass.ts @@ -20,7 +20,7 @@ export class CreateAccessPass { throw new AccessPassFailedError(AccessPassFailureReason.SHARING_CODE_EXPIRED); } - const accessPass = new AccessPass(new UserId(userId), sharingCode.userId); + const accessPass = new AccessPass(new UserId(userId), sharingCode.userId, sharingCode.accessDuration); return this.accessPassRepository.save(accessPass); } diff --git a/src/database/migrations/V8__addDurationToAccessPass.ts b/src/database/migrations/V8__addDurationToAccessPass.ts new file mode 100644 index 0000000..deb0ca5 --- /dev/null +++ b/src/database/migrations/V8__addDurationToAccessPass.ts @@ -0,0 +1,21 @@ +import knex from 'knex'; + +const ACCESS_PASS_TABLE = 'access_pass'; + +export async function up(db: knex) { + await db.schema.table(ACCESS_PASS_TABLE, (table) => { + table.integer('duration'); + }); + + await db(ACCESS_PASS_TABLE).where({ duration: null }).update({ duration: 60 }); + + await db.schema.alterTable(ACCESS_PASS_TABLE, (table) => { + table.integer('duration').notNullable().alter(); + }); +} + +export async function down(db: knex) { + await db.schema.alterTable(ACCESS_PASS_TABLE, (table) => { + table.dropColumn('duration'); + }); +} diff --git a/src/domain/model/accessPass/AccessPass.test.ts b/src/domain/model/accessPass/AccessPass.test.ts index 3e478cf..d123a2b 100644 --- a/src/domain/model/accessPass/AccessPass.test.ts +++ b/src/domain/model/accessPass/AccessPass.test.ts @@ -11,7 +11,7 @@ describe('Access Pass', () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); expect(accessPass.actorUserId).toBe(actorUserId); expect(accessPass.subjectUserId).toBe(subjectUserId); @@ -25,11 +25,15 @@ describe('Access Pass', () => { MockDate.set('2020-11-03 00:00:00'); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); + + // More than default duration + MockDate.set('2020-11-03 01:01:00'); expect(accessPass.isExpired()).toBe(false); - MockDate.set('2020-11-03 01:01:00'); + // More than specified duration + MockDate.set('2020-11-03 02:01:00'); expect(accessPass.isExpired()).toBe(true); }); diff --git a/src/domain/model/accessPass/AccessPass.ts b/src/domain/model/accessPass/AccessPass.ts index 77c51a5..8434210 100644 --- a/src/domain/model/accessPass/AccessPass.ts +++ b/src/domain/model/accessPass/AccessPass.ts @@ -1,13 +1,13 @@ import { v4 as uuidv4 } from 'uuid'; import { UserId } from '../user/UserId'; -// 1 hour -const ACCESS_REQUEST_LIFETIME_MSEC = 60 * 60 * 1_000; +const DEFAULT_DURATION_MINUTES = 60; export class AccessPass { constructor( readonly actorUserId: UserId, readonly subjectUserId: UserId, + readonly duration: number = DEFAULT_DURATION_MINUTES, readonly id: string = uuidv4(), readonly creationTime: Date = new Date() ) {} @@ -17,6 +17,7 @@ export class AccessPass { } public expirationTime(): Date { - return new Date(this.creationTime.getTime() + ACCESS_REQUEST_LIFETIME_MSEC); + const millisecondDuration = this.duration * 60 * 1_000; + return new Date(this.creationTime.getTime() + millisecondDuration); } } diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts index 5405983..5ad6855 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts @@ -16,7 +16,7 @@ describe('PsqlAccessPassRepository', () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const accessPass = new AccessPass(actorUserId, subjectUserId); + const accessPass = new AccessPass(actorUserId, subjectUserId, 120); await psqlAccessPassRepository.save(accessPass); const persistedAccessPass = await psqlAccessPassRepository.findByUserIds(actorUserId, subjectUserId); @@ -28,11 +28,11 @@ describe('PsqlAccessPassRepository', () => { const actorUserId = new UserId(); const subjectUserId = new UserId(); - const firstPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-01')); + const firstPass = new AccessPass(actorUserId, subjectUserId, 60, uuidv4(), new Date('2020-01-01')); - const secondPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-02')); + const secondPass = new AccessPass(actorUserId, subjectUserId, 120, uuidv4(), new Date('2020-01-02')); - const thirdPass = new AccessPass(actorUserId, subjectUserId, uuidv4(), new Date('2020-01-03')); + const thirdPass = new AccessPass(actorUserId, subjectUserId, 180, uuidv4(), new Date('2020-01-03')); await psqlAccessPassRepository.save(firstPass); await psqlAccessPassRepository.save(thirdPass); diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.ts index 8ee6315..2459a51 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.ts @@ -14,6 +14,7 @@ export class PsqlAccessPassRepository implements AccessPassRepository { 'id', 'actor_user_id as actorUserId', 'subject_user_id as subjectUserId', + 'duration', 'creation_time as creationTime', ]) .where({ @@ -30,6 +31,7 @@ export class PsqlAccessPassRepository implements AccessPassRepository { return new AccessPass( new UserId(linkRow.actorUserId), new UserId(linkRow.subjectUserId), + linkRow.duration, linkRow.id, linkRow.creationTime ); @@ -39,13 +41,14 @@ export class PsqlAccessPassRepository implements AccessPassRepository { return await this.db .raw( ` - insert into "${ACCESS_PASS_TABLE_NAME}" (id, actor_user_id, subject_user_id, creation_time) - values (:id, :actor_user_id, :subject_user_id, :creation_time) + insert into "${ACCESS_PASS_TABLE_NAME}" (id, actor_user_id, subject_user_id, duration, creation_time) + values (:id, :actor_user_id, :subject_user_id, :duration, :creation_time) `, { id: accessPass.id, actor_user_id: accessPass.actorUserId.value, subject_user_id: accessPass.subjectUserId.value, + duration: accessPass.duration, creation_time: accessPass.creationTime, } ) diff --git a/src/presentation/api/access-sharing/AccessPassController.test.ts b/src/presentation/api/access-sharing/AccessPassController.test.ts index c5db18f..42d7ca4 100644 --- a/src/presentation/api/access-sharing/AccessPassController.test.ts +++ b/src/presentation/api/access-sharing/AccessPassController.test.ts @@ -10,6 +10,7 @@ import { getTokenForUser } from '../../../test/authentication'; import { v4 as uuidv4 } from 'uuid'; import { aUserWithAllInformation } from '../../../test/domainFactories'; import { RootController } from '../RootController'; +import MockDate from 'mockdate'; describe('sharing code endpoints', () => { const app = new RootController().expressApp(); @@ -68,9 +69,11 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, 15, uuidv4(), new Date()); + const sharingCode = new SharingCode(user2.id, 120, uuidv4(), new Date()); await sharingCodeRepository.save(sharingCode); + MockDate.set('2020-05-04T00:00:00Z'); + await request(app) .post(`/api/v1/users/${user1.id.value}/access-passes`) .send({ code: sharingCode.code }) @@ -79,7 +82,7 @@ describe('sharing code endpoints', () => { .expect((response) => { const accessPass = response.body; expect(accessPass.userId).toBe(user2.id.value); - expect(accessPass.expiryTime).toBeDefined(); + expect(accessPass.expiryTime).toBe('2020-05-04T02:00:00.000Z'); }); }); }); diff --git a/src/presentation/api/tests/TestController.test.ts b/src/presentation/api/tests/TestController.test.ts index 6b92333..0450008 100644 --- a/src/presentation/api/tests/TestController.test.ts +++ b/src/presentation/api/tests/TestController.test.ts @@ -85,7 +85,7 @@ describe('TestController', () => { const subjectUser = await userRepository.save(aNewUser()); await testRepository.save(aTest(subjectUser.id)); - const accessPass = new AccessPass(actorUser.id, subjectUser.id, uuidv4(), new Date('1970-01-01')); + const accessPass = new AccessPass(actorUser.id, subjectUser.id, 60, uuidv4(), new Date('1970-01-01')); await accessPassRepository.save(accessPass); diff --git a/src/presentation/api/users/UserController.test.ts b/src/presentation/api/users/UserController.test.ts index 0ddf65f..55f75fe 100644 --- a/src/presentation/api/users/UserController.test.ts +++ b/src/presentation/api/users/UserController.test.ts @@ -178,7 +178,7 @@ describe('user endpoints', () => { const actorUser = await userRepository.save(aNewUser()); const subjectUser = await userRepository.save(aNewUser()); - const accessPass = new AccessPass(actorUser.id, subjectUser.id, uuidv4(), new Date('1970-01-01')); + const accessPass = new AccessPass(actorUser.id, subjectUser.id, 60, uuidv4(), new Date('1970-01-01')); await accessPassRepository.save(accessPass); From e1ca2d81bdfe94c1193e911682e374c25b5a34eb Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Mon, 4 May 2020 11:17:27 +0100 Subject: [PATCH 6/7] Change default access pass duration back to 60 minutes --- src/application/service/access-sharing/CreateSharingCode.ts | 2 +- src/database/migrations/V7__addSharingCodeAccessDuration.ts | 2 +- src/domain/model/sharingCode/SharingCode.ts | 3 ++- .../api/access-sharing/AccessPassController.test.ts | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/application/service/access-sharing/CreateSharingCode.ts b/src/application/service/access-sharing/CreateSharingCode.ts index 1fb1b5d..5fbb0ba 100644 --- a/src/application/service/access-sharing/CreateSharingCode.ts +++ b/src/application/service/access-sharing/CreateSharingCode.ts @@ -5,7 +5,7 @@ import { UserId } from '../../../domain/model/user/UserId'; export class CreateSharingCode { constructor(private sharingCodeRepository: SharingCodeRepository) {} - public async withUserId(userId: string, accessDuration: number = 15): Promise { + public async withUserId(userId: string, accessDuration?: number): Promise { const sharingCode = new SharingCode(new UserId(userId), accessDuration); return this.sharingCodeRepository.save(sharingCode); } diff --git a/src/database/migrations/V7__addSharingCodeAccessDuration.ts b/src/database/migrations/V7__addSharingCodeAccessDuration.ts index 880bcbb..4cbd475 100644 --- a/src/database/migrations/V7__addSharingCodeAccessDuration.ts +++ b/src/database/migrations/V7__addSharingCodeAccessDuration.ts @@ -7,7 +7,7 @@ export async function up(db: knex) { table.integer('access_duration'); }); - await db(SHARING_CODE_TABLE).where({ access_duration: null }).update({ access_duration: 15 }); + await db(SHARING_CODE_TABLE).where({ access_duration: null }).update({ access_duration: 60 }); await db.schema.alterTable(SHARING_CODE_TABLE, (table) => { table.integer('access_duration').notNullable().alter(); diff --git a/src/domain/model/sharingCode/SharingCode.ts b/src/domain/model/sharingCode/SharingCode.ts index af51a8f..4766986 100644 --- a/src/domain/model/sharingCode/SharingCode.ts +++ b/src/domain/model/sharingCode/SharingCode.ts @@ -3,11 +3,12 @@ import { UserId } from '../user/UserId'; // 5 minutes const SHARING_CODE_LIFETIME_MSEC = 5 * 60 * 1_000; +const ACCESS_PASS_DURATION_MINUTES = 60; export class SharingCode { constructor( readonly userId: UserId, - readonly accessDuration: number = 15, // Minutes + readonly accessDuration: number = ACCESS_PASS_DURATION_MINUTES, readonly code: string = uuidv4(), readonly creationTime: Date = new Date() ) {} diff --git a/src/presentation/api/access-sharing/AccessPassController.test.ts b/src/presentation/api/access-sharing/AccessPassController.test.ts index c5db18f..c5e932f 100644 --- a/src/presentation/api/access-sharing/AccessPassController.test.ts +++ b/src/presentation/api/access-sharing/AccessPassController.test.ts @@ -51,7 +51,7 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, 15, uuidv4(), new Date('1970-01-01')); + const sharingCode = new SharingCode(user2.id, 60, uuidv4(), new Date('1970-01-01')); await sharingCodeRepository.save(sharingCode); await request(app) @@ -68,7 +68,7 @@ describe('sharing code endpoints', () => { await userRepository.save(user1); await userRepository.save(user2); - const sharingCode = new SharingCode(user2.id, 15, uuidv4(), new Date()); + const sharingCode = new SharingCode(user2.id, 60, uuidv4(), new Date()); await sharingCodeRepository.save(sharingCode); await request(app) From d93d3e7b1d2dd122e7b870c23ffae8a1160fed12 Mon Sep 17 00:00:00 2001 From: Steve Pole Date: Wed, 6 May 2020 00:25:13 +0100 Subject: [PATCH 7/7] Add the ability to list users you have access codes for --- src/application/service/index.ts | 3 +- .../service/users/GetAccessibleUsers.ts | 18 +++++++ .../model/accessPass/AccessPassRepository.ts | 2 + src/domain/model/user/UserRepository.ts | 1 + .../PsqlAccessPassRepository.test.ts | 30 ++++++++++- .../persistence/PsqlAccessPassRepository.ts | 50 +++++++++++++------ .../persistence/PsqlUserRepository.ts | 34 ++++++++----- .../api/users/UserController.test.ts | 29 +++++++++++ src/presentation/api/users/UserController.ts | 27 ++++++++-- src/presentation/dtos/users/UserDTO.ts | 6 +++ .../transformers/UserTransformer.ts | 10 +++- src/test/domainFactories.ts | 5 ++ 12 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 src/application/service/users/GetAccessibleUsers.ts diff --git a/src/application/service/index.ts b/src/application/service/index.ts index 0e1a9c3..12ebd75 100644 --- a/src/application/service/index.ts +++ b/src/application/service/index.ts @@ -26,6 +26,7 @@ import { import { DokobitAuthenticationProvider } from '../../infrastructure/idAuthentication/DokobitAuthenticationProvider'; import { GetUser } from './users/GetUser'; import { UpdateUser } from './users/UpdateUser'; +import { GetAccessibleUsers } from './users/GetAccessibleUsers'; import { GetCountries } from './users/GetCountries'; import { GetTestTypes } from './tests/GetTestTypes'; import { CreateSharingCode } from './access-sharing/CreateSharingCode'; @@ -95,8 +96,8 @@ export const createMagicLink = new CreateNewMagicLink( ); export const getUser = new GetUser(userRepository); - export const updateUser = new UpdateUser(userRepository); +export const getAccessibleUsers = new GetAccessibleUsers(userRepository, accessPassRepository); export const getExistingOrCreateNewUser = new GetExistingOrCreateNewUser( userRepository, diff --git a/src/application/service/users/GetAccessibleUsers.ts b/src/application/service/users/GetAccessibleUsers.ts new file mode 100644 index 0000000..468d08f --- /dev/null +++ b/src/application/service/users/GetAccessibleUsers.ts @@ -0,0 +1,18 @@ +import { User } from '../../../domain/model/user/User'; +import { UserId } from '../../../domain/model/user/UserId'; +import { UserRepository } from '../../../domain/model/user/UserRepository'; +import { AccessPassRepository } from '../../../domain/model/accessPass/AccessPassRepository'; + +export class GetAccessibleUsers { + constructor(private userRepository: UserRepository, private accessPassRepository: AccessPassRepository) {} + + async byActorId(id: string): Promise> { + const accessPasses = await this.accessPassRepository.findByActorId(new UserId(id)); + + const userIds = accessPasses + .filter((accessPass) => !accessPass.isExpired()) + .map((accessPass) => accessPass.subjectUserId); + + return this.userRepository.findByUserIds(userIds); + } +} diff --git a/src/domain/model/accessPass/AccessPassRepository.ts b/src/domain/model/accessPass/AccessPassRepository.ts index 726e74e..3564c2c 100644 --- a/src/domain/model/accessPass/AccessPassRepository.ts +++ b/src/domain/model/accessPass/AccessPassRepository.ts @@ -5,4 +5,6 @@ export interface AccessPassRepository { save(accessPass: AccessPass): Promise; findByUserIds(actorUserId: UserId, subjectUserId: UserId): Promise; + + findByActorId(actorUserId: UserId): Promise>; } diff --git a/src/domain/model/user/UserRepository.ts b/src/domain/model/user/UserRepository.ts index 9fce754..66855da 100644 --- a/src/domain/model/user/UserRepository.ts +++ b/src/domain/model/user/UserRepository.ts @@ -8,6 +8,7 @@ export interface UserRepository { save(user: User): Promise; findByUserId(userId: UserId): Promise; + findByUserIds(userIds: Array): Promise>; findByAuthenticationDetails(authenticationDetails: AuthenticationDetails): Promise; } diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts index 5ad6855..badee56 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.test.ts @@ -4,12 +4,14 @@ import { AccessPass } from '../../domain/model/accessPass/AccessPass'; import { UserId } from '../../domain/model/user/UserId'; import { v4 as uuidv4 } from 'uuid'; import { cleanupDatabase } from '../../test/cleanupDatabase'; +import MockDate from 'mockdate'; describe('PsqlAccessPassRepository', () => { const psqlAccessPassRepository = new PsqlAccessPassRepository(database); beforeEach(async () => { await cleanupDatabase(); + MockDate.reset(); }); it('inserts new and retrieves a access pass', async () => { @@ -29,9 +31,7 @@ describe('PsqlAccessPassRepository', () => { const subjectUserId = new UserId(); const firstPass = new AccessPass(actorUserId, subjectUserId, 60, uuidv4(), new Date('2020-01-01')); - const secondPass = new AccessPass(actorUserId, subjectUserId, 120, uuidv4(), new Date('2020-01-02')); - const thirdPass = new AccessPass(actorUserId, subjectUserId, 180, uuidv4(), new Date('2020-01-03')); await psqlAccessPassRepository.save(firstPass); @@ -42,6 +42,32 @@ describe('PsqlAccessPassRepository', () => { expect(persistedAccessPass).toEqual(thirdPass); }); + + it('retrieves all access passes for the actor with latet first', async () => { + const actorUserId = new UserId(); + + MockDate.set('2020-01-01T00:00:00Z'); + const firstPass = new AccessPass(actorUserId, new UserId(), 60); + + MockDate.set('2020-01-01T00:00:01Z'); + const secondPass = new AccessPass(actorUserId, new UserId(), 120); + + MockDate.set('2020-01-01T00:00:02Z'); + const thirdPass = new AccessPass(actorUserId, new UserId(), 180); + + await psqlAccessPassRepository.save(firstPass); + await psqlAccessPassRepository.save(thirdPass); + await psqlAccessPassRepository.save(secondPass); + + MockDate.set('2020-01-01T01:01:00Z'); + + const accessPasses = await psqlAccessPassRepository.findByActorId(actorUserId); + + expect(accessPasses.length).toBe(3); + expect(accessPasses[0]).toEqual(thirdPass); + expect(accessPasses[1]).toEqual(secondPass); + expect(accessPasses[2]).toEqual(firstPass); + }); }); afterAll(() => { diff --git a/src/infrastructure/persistence/PsqlAccessPassRepository.ts b/src/infrastructure/persistence/PsqlAccessPassRepository.ts index 2459a51..d1a3a4a 100644 --- a/src/infrastructure/persistence/PsqlAccessPassRepository.ts +++ b/src/infrastructure/persistence/PsqlAccessPassRepository.ts @@ -5,18 +5,20 @@ import { UserId } from '../../domain/model/user/UserId'; const ACCESS_PASS_TABLE_NAME = 'access_pass'; +const COLUMNS = [ + 'id', + 'actor_user_id as actorUserId', + 'subject_user_id as subjectUserId', + 'duration', + 'creation_time as creationTime', +]; + export class PsqlAccessPassRepository implements AccessPassRepository { constructor(private db: knex) {} async findByUserIds(actorUserId: UserId, subjectUserId: UserId) { const linkRow: any = await this.db(ACCESS_PASS_TABLE_NAME) - .select([ - 'id', - 'actor_user_id as actorUserId', - 'subject_user_id as subjectUserId', - 'duration', - 'creation_time as creationTime', - ]) + .select(COLUMNS) .where({ actor_user_id: actorUserId.value, subject_user_id: subjectUserId.value, @@ -28,13 +30,23 @@ export class PsqlAccessPassRepository implements AccessPassRepository { return null; } - return new AccessPass( - new UserId(linkRow.actorUserId), - new UserId(linkRow.subjectUserId), - linkRow.duration, - linkRow.id, - linkRow.creationTime - ); + return convertRowToAccessPass(linkRow); + } + + async findByActorId(actorUserId: UserId) { + // TODO group by userId to get latest, but also get latest duration, so can't just use MAX + const rows: any = await this.db(ACCESS_PASS_TABLE_NAME) + .select(COLUMNS) + .where({ + actor_user_id: actorUserId.value, + }) + .orderBy('creation_time', 'desc'); + + if (!rows) { + return []; + } + + return rows.map(convertRowToAccessPass); } async save(accessPass: AccessPass) { @@ -57,3 +69,13 @@ export class PsqlAccessPassRepository implements AccessPassRepository { }); } } + +function convertRowToAccessPass(linkRow: any) { + return new AccessPass( + new UserId(linkRow.actorUserId), + new UserId(linkRow.subjectUserId), + linkRow.duration, + linkRow.id, + linkRow.creationTime + ); +} diff --git a/src/infrastructure/persistence/PsqlUserRepository.ts b/src/infrastructure/persistence/PsqlUserRepository.ts index c47830e..e1bee9a 100644 --- a/src/infrastructure/persistence/PsqlUserRepository.ts +++ b/src/infrastructure/persistence/PsqlUserRepository.ts @@ -97,6 +97,12 @@ export class PsqlUserRepository implements UserRepository { return this.extractUserAndPopulateRoles(userRow); } + async findByUserIds(userIds: Array): Promise> { + const idValues = userIds.map((userId) => userId.value); + const rows: any = await this.db(USER_TABLE_NAME).whereIn('id', idValues).select(USER_TABLE_COLUMNS); + return rows.map(mapRowToUser); + } + private async saveRoleAssignment(roleAssignment: RoleAssignmentAction, context = this.db) { await context('role_to_user_assignment').insert({ id: roleAssignment.id.value, @@ -110,18 +116,7 @@ export class PsqlUserRepository implements UserRepository { } private async extractUserAndPopulateRoles(userRow: any) { - const user = new User( - new UserId(userRow.id), - new AuthenticationDetails( - new AuthenticationMethod(userRow.authenticationMethod), - new AuthenticationIdentifier(userRow.authenticationIdentifier) - ), - userRow.email ? new Email(userRow.email) : undefined, - fromDbProfile(userRow.profile as DbProfile | undefined), - fromDbAddress(userRow.address as DbAddress | undefined), - userRow.creationTime, - userRow.modificationTime - ); + const user = mapRowToUser(userRow); const roleAssignmentActions = await this.getRoleAssignments(user); Reflect.set(user.roleAssignments, 'assignmentActions', roleAssignmentActions); Reflect.set(user.roleAssignments, 'newAssignmentActions', []); @@ -188,6 +183,21 @@ export class PsqlUserRepository implements UserRepository { } } +function mapRowToUser(userRow: any): User { + return new User( + new UserId(userRow.id), + new AuthenticationDetails( + new AuthenticationMethod(userRow.authenticationMethod), + new AuthenticationIdentifier(userRow.authenticationIdentifier) + ), + userRow.email ? new Email(userRow.email) : undefined, + fromDbProfile(userRow.profile as DbProfile | undefined), + fromDbAddress(userRow.address as DbAddress | undefined), + userRow.creationTime, + userRow.modificationTime + ); +} + function toDbProfile(profile?: Profile): DbProfile | undefined { return profile ? { diff --git a/src/presentation/api/users/UserController.test.ts b/src/presentation/api/users/UserController.test.ts index 55f75fe..95af957 100644 --- a/src/presentation/api/users/UserController.test.ts +++ b/src/presentation/api/users/UserController.test.ts @@ -7,18 +7,22 @@ import { accessPassRepository, userRepository } from '../../../infrastructure/pe import { User } from '../../../domain/model/user/User'; import { AccessPass } from '../../../domain/model/accessPass/AccessPass'; import { CREATE_USERS } from '../../../domain/model/authentication/Permissions'; + import { anAddress, aNewUser, aUserWithAllInformation, + anAccessPass, magicLinkAuthenticationDetails, estonianIdAuthenticationDetails, } from '../../../test/domainFactories'; + import { persistedUserWithRoleAndPermissions } from '../../../test/persistedEntities'; import { getTokenForUser } from '../../../test/authentication'; import { anApiAddress, anApiProfile } from '../../../test/apiFactories'; import { RootController } from '../RootController'; import { AuthenticationMethodType } from '../../../domain/model/user/AuthenticationMethod'; +import MockDate from 'mockdate'; describe('user endpoints', () => { const app = new RootController().expressApp(); @@ -105,6 +109,31 @@ describe('user endpoints', () => { }); }); + describe('GET /users', () => { + it('returns users for whom the actor has a pass', async () => { + const actor = await userRepository.save(aNewUser()); + + const userWithAccess = await userRepository.save(aNewUser()); + const userWithExpiredAccess = await userRepository.save(aNewUser()); + const userWithoutAccess = await userRepository.save(aNewUser()); + + MockDate.set('2020-01-01T00:00:00Z'); + await accessPassRepository.save(anAccessPass(actor.id, userWithAccess.id, 60)); + await accessPassRepository.save(anAccessPass(actor.id, userWithExpiredAccess.id, 15)); + MockDate.set('2020-01-01T00:30:00Z'); + + await request(app) + .get(`/api/v1/users`) + .set({ Authorization: `Bearer ${await getTokenForUser(actor)}` }) + .expect(200) + .expect((response) => { + const users = response.body; + expect(users.length).toEqual(1); + expect(userWithAccess.id.value).toEqual(userWithAccess.id.value); + }); + }); + }); + describe('GET /users/:id', () => { it('returns 404 if user is not found', async () => { const user = await userRepository.save(aNewUser()); diff --git a/src/presentation/api/users/UserController.ts b/src/presentation/api/users/UserController.ts index e733064..d0b6c9c 100644 --- a/src/presentation/api/users/UserController.ts +++ b/src/presentation/api/users/UserController.ts @@ -1,10 +1,16 @@ -import { accessManagerFactory, getUser, updateUser, getExistingOrCreateNewUser } from '../../../application/service'; +import { + accessManagerFactory, + getUser, + updateUser, + getExistingOrCreateNewUser, + getAccessibleUsers, +} from '../../../application/service'; import { UserTransformer } from '../../transformers/UserTransformer'; import { UserId } from '../../../domain/model/user/UserId'; import { ApiError, apiErrorCodes } from '../../dtos/ApiError'; -import { UserDTO, RestrictedUserDTO } from '../../dtos/users/UserDTO'; +import { UserDTO, RestrictedUserDTO, SharedUserDTO } from '../../dtos/users/UserDTO'; import { Authorized, @@ -19,6 +25,7 @@ import { UseAfter, UseBefore, } from 'routing-controllers'; + import { hasPermission } from '../../middleware/hasPermission'; import { CREATE_USERS } from '../../../domain/model/authentication/Permissions'; import { UserErrorHandler } from './UserErrorHandler'; @@ -55,14 +62,28 @@ export class UserController { return this.userTransformer.toRestrictedUserDTO(user); } + @Get('') + async getUsers(@CurrentUser({ required: true }) actor: User): Promise> { + const users = await getAccessibleUsers.byActorId(actor.id.value); + return users.map(this.userTransformer.toSharedUserDTO); + } + @Get('/:id') - async getById(@Param('id') idValue: string, @CurrentUser({ required: true }) actor: User): Promise { + async getById( + @Param('id') idValue: string, + @CurrentUser({ required: true }) actor: User + ): Promise { const id = new UserId(idValue); await this.validateCanGetUser(actor, id); const user = await getUser.byId(idValue); + // Share less data if requesting another user + if (id.value !== actor.id.value) { + return this.userTransformer.toSharedUserDTO(user); + } + return this.userTransformer.toUserDTO(user); } diff --git a/src/presentation/dtos/users/UserDTO.ts b/src/presentation/dtos/users/UserDTO.ts index 3c1dd09..af0c662 100644 --- a/src/presentation/dtos/users/UserDTO.ts +++ b/src/presentation/dtos/users/UserDTO.ts @@ -15,3 +15,9 @@ export interface RestrictedUserDTO { id: string; authenticationDetails: AuthenticationDetailsDTO; } + +export interface SharedUserDTO { + id: string; + profile?: ProfileDTO; + address?: AddressDTO; +} diff --git a/src/presentation/transformers/UserTransformer.ts b/src/presentation/transformers/UserTransformer.ts index fbbe925..a4bd3fc 100644 --- a/src/presentation/transformers/UserTransformer.ts +++ b/src/presentation/transformers/UserTransformer.ts @@ -1,7 +1,7 @@ import { User } from '../../domain/model/user/User'; import { Address } from '../../domain/model/user/Address'; import { Profile } from '../../domain/model/user/Profile'; -import { UserDTO, RestrictedUserDTO } from '../dtos/users/UserDTO'; +import { UserDTO, RestrictedUserDTO, SharedUserDTO } from '../dtos/users/UserDTO'; import { ProfileDTO } from '../dtos/users/ProfileDTO'; import { AddressDTO } from '../dtos/users/AddressDTO'; import { AuthenticationDetails } from '../../domain/model/user/AuthenticationDetails'; @@ -19,6 +19,14 @@ export class UserTransformer { }; } + public toSharedUserDTO(user: User): SharedUserDTO { + return { + id: user.id.value, + profile: user.profile ? this.toProfileDTO(user.profile) : undefined, + address: user.address ? this.toAddressDTO(user.address) : undefined, + }; + } + public toRestrictedUserDTO(user: User): RestrictedUserDTO { return { id: user.id.value, diff --git a/src/test/domainFactories.ts b/src/test/domainFactories.ts index c48033e..b39d13a 100644 --- a/src/test/domainFactories.ts +++ b/src/test/domainFactories.ts @@ -14,6 +14,7 @@ import { TestTypeId } from '../domain/model/test/testType/TestTypeId'; import { Test } from '../domain/model/test/Test'; import { TestId } from '../domain/model/test/TestId'; import { Results } from '../domain/model/test/Results'; +import { AccessPass } from '../domain/model/accessPass/AccessPass'; import { ConfidenceLevel } from '../domain/model/test/ConfidenceLevel'; import { InterpretationRules } from '../domain/model/test/interpretation/InterpretationRules'; import { InterpretationTheme } from '../domain/model/test/interpretation/Interpretation'; @@ -101,6 +102,10 @@ export function aResult(userId = new UserId(), details = { c: true, igg: true, i return new Results(userId, details, ConfidenceLevel.LOW, notes); } +export function anAccessPass(actorId: UserId, subjectId: UserId, duration: number = 60) { + return new AccessPass(actorId, subjectId, duration); +} + export function antibodyTestType() { return new TestType( new TestTypeId(),