diff --git a/packages/api-gateway/src/Controller/v1/ItemSharesController.ts b/packages/api-gateway/src/Controller/v1/ItemSharesController.ts index b8a9aaa5a..9eea5c728 100644 --- a/packages/api-gateway/src/Controller/v1/ItemSharesController.ts +++ b/packages/api-gateway/src/Controller/v1/ItemSharesController.ts @@ -10,8 +10,8 @@ export class ItemSharesController extends BaseHttpController { super() } - @all('(/*)?') + @all('*') async subscriptions(request: Request, response: Response): Promise { - await this.httpService.callSyncingServer(request, response, request.path, request.body) + await this.httpService.callSyncingServer(request, response, request.path.replace('/v1/', ''), request.body) } } diff --git a/packages/syncing-server/bin/server.ts b/packages/syncing-server/bin/server.ts index f8115f400..76fddb593 100644 --- a/packages/syncing-server/bin/server.ts +++ b/packages/syncing-server/bin/server.ts @@ -4,6 +4,7 @@ import 'newrelic' import '../src/Controller/HealthCheckController' import '../src/Controller/ItemsController' +import '../src/Controller/ItemSharesController' import helmet from 'helmet' import * as cors from 'cors' diff --git a/packages/syncing-server/migrations/mysql/1683714734321-create_item_share.ts b/packages/syncing-server/migrations/mysql/1683714734321-create_item_share.ts index b1f2bc7a6..d7dbc50b6 100644 --- a/packages/syncing-server/migrations/mysql/1683714734321-create_item_share.ts +++ b/packages/syncing-server/migrations/mysql/1683714734321-create_item_share.ts @@ -9,9 +9,19 @@ export class CreateItemShare1683714734321 implements MigrationInterface { columns: [ new TableColumn({ name: 'uuid', - type: 'uuid', + type: 'varchar', + length: '36', isPrimary: true, - default: 'gen_random_uuid()', + }), + new TableColumn({ + name: 'user_uuid', + type: 'varchar', + length: '36', + }), + new TableColumn({ + name: 'item_uuid', + type: 'varchar', + length: '36', }), new TableColumn({ name: 'share_token', @@ -35,16 +45,6 @@ export class CreateItemShare1683714734321 implements MigrationInterface { type: 'text', isNullable: true, }), - new TableColumn({ - name: 'user_uuid', - type: 'varchar', - length: '36', - }), - new TableColumn({ - name: 'item_uuid', - type: 'varchar', - length: '36', - }), new TableColumn({ name: 'expired', type: 'tinyint', diff --git a/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts b/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts index 687861328..2a17ee618 100644 --- a/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts +++ b/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts @@ -1,3 +1,5 @@ +import { ItemShare } from './../Domain/ItemShare/ItemShare' +import { TypeORMItemShareRepository } from './../Infra/TypeORM/TypeORMItemShareRepository' import * as winston from 'winston' import { Container, interfaces } from 'inversify' @@ -19,7 +21,6 @@ import { Timer, TimerInterface } from '@standardnotes/time' import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface' import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator' import { ItemShareRepositoryInterface } from '../Domain/ItemShare/ItemShareRepositoryInterface' -import { TypeORMItemShareRepository } from '../Infra/TypeORM/TypeORMItemShareRepository' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -81,11 +82,14 @@ export class CommonContainerConfigLoader { container .bind(TYPES.ItemShareRepository) .toDynamicValue((context: interfaces.Context) => { - return new TypeORMItemShareRepository(context.container.get(TYPES.ORMItemRepository)) + return new TypeORMItemShareRepository(context.container.get(TYPES.ORMItemShareRepository)) }) // ORM container.bind>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item)) + container + .bind>(TYPES.ORMItemShareRepository) + .toDynamicValue(() => AppDataSource.getRepository(ItemShare)) // Projectors container diff --git a/packages/syncing-server/src/Bootstrap/DataSource.ts b/packages/syncing-server/src/Bootstrap/DataSource.ts index 1a7c24905..2dc5096b8 100644 --- a/packages/syncing-server/src/Bootstrap/DataSource.ts +++ b/packages/syncing-server/src/Bootstrap/DataSource.ts @@ -1,3 +1,4 @@ +import { ItemShare } from './../Domain/ItemShare/ItemShare' import { DataSource, LoggerOptions } from 'typeorm' import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions' import { Item } from '../Domain/Item/Item' @@ -38,7 +39,7 @@ const replicationConfig = { const commonDataSourceOptions = { maxQueryExecutionTime, - entities: [Item], + entities: [Item, ItemShare], migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`], migrationsRun: true, logging: env.get('DB_DEBUG_LEVEL'), diff --git a/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts b/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts index 244353269..92de95d9d 100644 --- a/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts +++ b/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts @@ -36,6 +36,7 @@ import { ItemShareService } from '../Domain/ItemShare/ItemShareService' import { ItemShareFactoryInterface } from '../Domain/ItemShare/ItemShareFactoryInterface' import { ItemShareFactory } from '../Domain/ItemShare/ItemShareFactory' import { ShareItemUseCase } from '../Domain/UseCase/ItemShare/ShareItemUseCase' +import { GetUserItemSharesUseCase } from '../Domain/UseCase/ItemShare/GetUserItemSharesUseCase' export class ServerContainerConfigLoader extends CommonContainerConfigLoader { private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 @@ -100,6 +101,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { container.bind(TYPES.UpdateSharedItem).toDynamicValue((context: interfaces.Context) => { return new UpdateSharedItemUseCase(context.container.get(TYPES.ItemShareService)) }) + container.bind(TYPES.GetUserItemShares).toDynamicValue((context: interfaces.Context) => { + return new GetUserItemSharesUseCase(context.container.get(TYPES.ItemShareService)) + }) // Services container.bind(TYPES.ItemService).toDynamicValue((context: interfaces.Context) => { @@ -119,7 +123,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { ) }) // Services - container.bind(TYPES.ItemService).toDynamicValue((context: interfaces.Context) => { + container.bind(TYPES.ItemShareService).toDynamicValue((context: interfaces.Context) => { return new ItemShareService( context.container.get(TYPES.ItemShareRepository), context.container.get(TYPES.ItemShareFactory), @@ -152,7 +156,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { container.bind(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => { return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector)) }) - container.bind(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => { + container.bind(TYPES.ItemShareFactory).toDynamicValue((context: interfaces.Context) => { return new ItemShareFactory(context.container.get(TYPES.Timer)) }) diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index f0e01240d..322b4488d 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -11,6 +11,7 @@ const TYPES = { ItemShareRepository: Symbol.for('ItemShareRepository'), // ORM ORMItemRepository: Symbol.for('ORMItemRepository'), + ORMItemShareRepository: Symbol.for('ORMItemShareRepository'), // Middleware AuthMiddleware: Symbol.for('AuthMiddleware'), // Projectors @@ -42,6 +43,7 @@ const TYPES = { ShareItem: Symbol.for('ShareItem'), GetSharedItem: Symbol.for('GetSharedItem'), UpdateSharedItem: Symbol.for('UpdateSharedItem'), + GetUserItemShares: Symbol.for('GetUserItemShares'), // Handlers AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'), DuplicateItemSyncedEventHandler: Symbol.for('DuplicateItemSyncedEventHandler'), diff --git a/packages/syncing-server/src/Controller/ItemSharesController.ts b/packages/syncing-server/src/Controller/ItemSharesController.ts index f89c55dd5..6efeeba6f 100644 --- a/packages/syncing-server/src/Controller/ItemSharesController.ts +++ b/packages/syncing-server/src/Controller/ItemSharesController.ts @@ -1,3 +1,4 @@ +import { GetUserItemSharesUseCase } from './../Domain/UseCase/ItemShare/GetUserItemSharesUseCase' import { UpdateSharedItemUseCase } from './../Domain/UseCase/ItemShare/UpdateSharedItemUseCase' import { GetSharedItemUseCase } from './../Domain/UseCase/ItemShare/GetSharedItemUseCase' import { Request, Response } from 'express' @@ -12,6 +13,7 @@ export class ItemSharesController extends BaseHttpController { @inject(TYPES.ShareItem) private shareItem: ShareItemUseCase, @inject(TYPES.GetSharedItem) private getSharedItem: GetSharedItemUseCase, @inject(TYPES.UpdateSharedItem) private updateSharedItem: UpdateSharedItemUseCase, + @inject(TYPES.GetUserItemShares) private getItemShares: GetUserItemSharesUseCase, ) { super() } @@ -19,11 +21,11 @@ export class ItemSharesController extends BaseHttpController { @httpPost('/') public async share(request: Request, response: Response): Promise { const result = await this.shareItem.execute({ - itemUuid: request.body.item_uuid, + itemUuid: request.body.itemUuid, userUuid: response.locals.user.uuid, - publicKey: request.body.public_key, - encryptedContentKey: request.body.encrypted_content_key, - contentType: request.body.content_type, + publicKey: request.body.publicKey, + encryptedContentKey: request.body.encryptedContentKey, + contentType: request.body.contentType, }) return this.json(result) @@ -32,8 +34,8 @@ export class ItemSharesController extends BaseHttpController { @httpPatch('/') public async updateSharedItemRequest(request: Request): Promise { const result = await this.updateSharedItem.execute({ - shareToken: request.body.share_token, - encryptedContentKey: request.body.encrypted_content_key, + shareToken: request.body.shareToken, + encryptedContentKey: request.body.encryptedContentKey, }) return this.json(result) @@ -42,26 +44,29 @@ export class ItemSharesController extends BaseHttpController { @httpGet('/item/:shareToken') public async getItemForShareToken(request: Request): Promise { const result = await this.getSharedItem.execute({ - shareToken: request.body.share_token, + shareToken: request.params.shareToken, }) if (result.success === false) { return this.notFound() } - return this.json(result.item) + return this.json(result) } @httpGet('/') - public async getItemSharesForUser(request: Request): Promise { - const result = await this.getSharedItem.execute({ - shareToken: request.body.share_token, + public async getItemSharesForUser( + _request: Request, + response: Response, + ): Promise { + const result = await this.getItemShares.execute({ + userUuid: response.locals.user.uuid, }) if (result.success === false) { return this.notFound() } - return this.json(result.item) + return this.json(result) } } diff --git a/packages/syncing-server/src/Domain/ItemShare/ItemShare.ts b/packages/syncing-server/src/Domain/ItemShare/ItemShare.ts index 0d3b0fe07..3f4d26323 100644 --- a/packages/syncing-server/src/Domain/ItemShare/ItemShare.ts +++ b/packages/syncing-server/src/Domain/ItemShare/ItemShare.ts @@ -11,9 +11,9 @@ export class ItemShare { type: 'varchar', name: 'share_token', length: 36, - nullable: true, + nullable: false, }) - declare shareToken: string | null + declare shareToken: string @Column({ name: 'content_type', diff --git a/packages/syncing-server/src/Domain/ItemShare/ItemShareFactory.ts b/packages/syncing-server/src/Domain/ItemShare/ItemShareFactory.ts index 5d9bcad41..8b83ed3df 100644 --- a/packages/syncing-server/src/Domain/ItemShare/ItemShareFactory.ts +++ b/packages/syncing-server/src/Domain/ItemShare/ItemShareFactory.ts @@ -8,6 +8,8 @@ export class ItemShareFactory implements ItemShareFactoryInterface { create(dto: { userUuid: string; itemShareHash: ItemShareHash }): ItemShare { const newItemShare = new ItemShare() newItemShare.uuid = dto.itemShareHash.uuid + newItemShare.userUuid = dto.userUuid + newItemShare.itemUuid = dto.itemShareHash.item_uuid newItemShare.shareToken = dto.itemShareHash.share_token newItemShare.encryptedContentKey = dto.itemShareHash.encrypted_content_key newItemShare.publicKey = dto.itemShareHash.public_key diff --git a/packages/syncing-server/src/Domain/ItemShare/ItemShareFactoryInterface.ts b/packages/syncing-server/src/Domain/ItemShare/ItemShareFactoryInterface.ts index 54e03d504..ada93eef2 100644 --- a/packages/syncing-server/src/Domain/ItemShare/ItemShareFactoryInterface.ts +++ b/packages/syncing-server/src/Domain/ItemShare/ItemShareFactoryInterface.ts @@ -3,6 +3,7 @@ import { ItemShare } from './ItemShare' export type ItemShareHash = { uuid: string + item_uuid: string share_token: string public_key: string encrypted_content_key: string diff --git a/packages/syncing-server/src/Domain/ItemShare/ItemShareRepositoryInterface.ts b/packages/syncing-server/src/Domain/ItemShare/ItemShareRepositoryInterface.ts index b716c303d..6e496c056 100644 --- a/packages/syncing-server/src/Domain/ItemShare/ItemShareRepositoryInterface.ts +++ b/packages/syncing-server/src/Domain/ItemShare/ItemShareRepositoryInterface.ts @@ -12,7 +12,7 @@ export type UserItemSharesQuery = { export interface ItemShareRepositoryInterface { create(itemShare: ItemShare): Promise remove(itemShare: ItemShare): Promise - updateEncryptedContentKey(shareToken: string, encryptedContentKey: string): Promise + updateEncryptedContentKey(dto: { shareToken: string; encryptedContentKey: string }): Promise deleteByShareToken(shareToken: string): Promise findByShareToken(shareToken: string): Promise findAll(query: UserItemSharesQuery): Promise diff --git a/packages/syncing-server/src/Domain/ItemShare/ItemShareService.ts b/packages/syncing-server/src/Domain/ItemShare/ItemShareService.ts index f20db96fe..de9d17546 100644 --- a/packages/syncing-server/src/Domain/ItemShare/ItemShareService.ts +++ b/packages/syncing-server/src/Domain/ItemShare/ItemShareService.ts @@ -44,6 +44,7 @@ export class ItemShareService implements ItemShareServiceInterface { userUuid: dto.userUuid, itemShareHash: { uuid, + item_uuid: dto.itemUuid, share_token: shareToken, public_key: dto.publicKey, encrypted_content_key: dto.encryptedContentKey, @@ -62,7 +63,10 @@ export class ItemShareService implements ItemShareServiceInterface { return false } - await this.itemShareRepository.updateEncryptedContentKey(itemShareItem.uuid, dto.encryptedContentKey) + await this.itemShareRepository.updateEncryptedContentKey({ + shareToken: itemShareItem.shareToken, + encryptedContentKey: dto.encryptedContentKey, + }) return true } diff --git a/packages/syncing-server/src/Domain/UseCase/ItemShare/GetItemSharesUseCase.ts b/packages/syncing-server/src/Domain/UseCase/ItemShare/GetUserItemSharesUseCase.ts similarity index 78% rename from packages/syncing-server/src/Domain/UseCase/ItemShare/GetItemSharesUseCase.ts rename to packages/syncing-server/src/Domain/UseCase/ItemShare/GetUserItemSharesUseCase.ts index 9621398d7..898472961 100644 --- a/packages/syncing-server/src/Domain/UseCase/ItemShare/GetItemSharesUseCase.ts +++ b/packages/syncing-server/src/Domain/UseCase/ItemShare/GetUserItemSharesUseCase.ts @@ -2,7 +2,7 @@ import { ItemShare } from '../../ItemShare/ItemShare' import { ItemShareServiceInterface } from '../../ItemShare/ItemShareServiceInterface' import { UseCaseInterface } from '../UseCaseInterface' -export type GetItemSharesResponse = +export type GetUserItemSharesResponse = | { success: true itemShares: ItemShare[] @@ -12,10 +12,10 @@ export type GetItemSharesResponse = message: string } -export class GetItemSharesUseCase implements UseCaseInterface { +export class GetUserItemSharesUseCase implements UseCaseInterface { constructor(private itemShareService: ItemShareServiceInterface) {} - async execute(dto: { userUuid: string }): Promise { + async execute(dto: { userUuid: string }): Promise { const result = await this.itemShareService.getUserItemShares(dto.userUuid) if (result === null) { diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemShareRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemShareRepository.ts index da40de6a1..302ed7018 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemShareRepository.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemShareRepository.ts @@ -13,15 +13,15 @@ export class TypeORMItemShareRepository implements ItemShareRepositoryInterface return this.ormRepository.remove(itemShare) } - async updateEncryptedContentKey(shareToken: string, encryptedContentKey: string): Promise { + async updateEncryptedContentKey(dto: { shareToken: string; encryptedContentKey: string }): Promise { await this.ormRepository .createQueryBuilder('item_share') .update() .set({ - encryptedContentKey, + encryptedContentKey: dto.encryptedContentKey, }) .where('share_token = :shareToken', { - shareToken, + shareToken: dto.shareToken, }) .execute() } @@ -38,7 +38,7 @@ export class TypeORMItemShareRepository implements ItemShareRepositoryInterface async findByShareToken(shareToken: string): Promise { return this.ormRepository .createQueryBuilder('item_share') - .where('item.share_token = :shareToken', { + .where('item_share.share_token = :shareToken', { shareToken, }) .getOne()