diff --git a/.env.example b/.env.example index 5e4f6e2..1f8af55 100644 --- a/.env.example +++ b/.env.example @@ -31,4 +31,10 @@ REDIS_PASSWORD="" REDIS_URL="redis://${REDIS_USER}:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}" # Vercel Blob Storage -BLOB_READ_WRITE_TOKEN="vercel_blob_rw_0511953119b0c1167283c7453a088727" # Fake vercel token \ No newline at end of file +BLOB_READ_WRITE_TOKEN="vercel_blob_rw_0511953119b0c1167283c7453a088727" # Fake vercel token + +# Pusher +PUSHER_APP_ID="1684269" +PUSHER_APP_KEY="3a4575271634ad5a09ef" +PUSHER_APP_SECRET="486036ded3c32d02bac3" +PUSHER_APP_CLUSTER="eu" \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 30bae75..a851069 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,12 @@ module.exports = { '^.+\\.(t|j)sx?$': ['@swc/jest'], }, setupFilesAfterEnv: ['./src/test/setupTest.ts'], - collectCoverageFrom: ['./src/**', '!./src/plugins/prisma.ts', '!./src/server.ts'], + collectCoverageFrom: [ + './src/**', + '!./src/server.ts', + '!./src/plugins/prisma.ts', + '!./src/plugins/pusher.ts', + ], coverageReporters: ['json-summary', 'text', 'html', 'json'], coveragePathIgnorePatterns: ['node_modules'], }; diff --git a/package-lock.json b/package-lock.json index aa5e642..86262db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "dotenv": "^16.3.1", "fastify": "^4.23.2", "fastify-i18n": "^1.1.1", - "fastify-plugin": "^4.5.1" + "fastify-plugin": "^4.5.1", + "pusher": "^5.1.3" }, "devDependencies": { "@swc/core": "^1.3.85", @@ -1842,6 +1843,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/semver": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", @@ -4602,6 +4612,15 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-base64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-1.1.0.tgz", + "integrity": "sha512-Nlhg7Z2dVC4/PTvIFkgVVNvPHSO2eR/Yd0XzhGiXCXEvWnptXlXa/clQ8aePPiMuxEGcWfzWbGw2Fe3d+Y3v1g==", + "bin": { + "is_base64": "bin/is-base64", + "is-base64": "bin/is-base64" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6712,6 +6731,22 @@ } ] }, + "node_modules/pusher": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/pusher/-/pusher-5.1.3.tgz", + "integrity": "sha512-Bmy5guFxQsbYSFLF3CM7GA2qE1zDJYn51PnNme9QlSjGguvkqUg4nj31PbgiLVDFK2sJvxPfx4JrB2HLgM3kaw==", + "dependencies": { + "@types/node-fetch": "^2.5.7", + "abort-controller": "^3.0.0", + "is-base64": "^1.1.0", + "node-fetch": "^2.6.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -7751,6 +7786,16 @@ "node": ">=0.10.0" } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 3459b46..4530b41 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "dotenv": "^16.3.1", "fastify": "^4.23.2", "fastify-i18n": "^1.1.1", - "fastify-plugin": "^4.5.1" + "fastify-plugin": "^4.5.1", + "pusher": "^5.1.3" }, "devDependencies": { "@swc/core": "^1.3.85", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ba1527b..d26c5dc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -49,16 +49,16 @@ model Item { ownerId Int parentId Int? - owner User @relation(fields: [ownerId], references: [id]) - parentItem Item? @relation("ItemToItem", fields: [parentId], references: [id]) - Items Item[] @relation("ItemToItem") - ItemFolder ItemFolder? - ItemBlob ItemBlob? - ItemDocs ItemDocs? - ItemSharing ItemSharing[] - ItemShortcut ItemShortcut? @relation("shortcutItem") - LinkedItemShortcut ItemShortcut[] @relation("linkedItem") - ItemStarred ItemStarred[] + owner User @relation(fields: [ownerId], references: [id]) + parentItem Item? @relation("ItemToItem", fields: [parentId], references: [id]) + Items Item[] @relation("ItemToItem") + ItemFolder ItemFolder? + ItemBlob ItemBlob? + ItemDocs ItemDocs? + ItemSharing ItemSharing[] + ItemShortcut ItemShortcut? @relation("shortcutItem") + LinkedItemShortcut ItemShortcut[] @relation("linkedItem") + ItemStarred ItemStarred[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -117,21 +117,21 @@ model ItemShortcut { shortcutItem Item @relation("shortcutItem", fields: [itemId], references: [id], onDelete: Cascade) linkedItem Item @relation("linkedItem", fields: [linkedItemId], references: [id], onDelete: Cascade) - + @@index([linkedItemId]) } model ItemStarred { - id Int @id @default(autoincrement()) - itemId Int - userId Int + id Int @id @default(autoincrement()) + itemId Int + userId Int item Item @relation(fields: [itemId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) - + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - + @@unique([itemId, userId]) @@index([itemId]) @@index([userId]) diff --git a/src/modules/auth/__test__/login.test.ts b/src/modules/auth/__test__/login.test.ts index 40b0a87..a679f84 100644 --- a/src/modules/auth/__test__/login.test.ts +++ b/src/modules/auth/__test__/login.test.ts @@ -1,6 +1,7 @@ import { User } from '@prisma/client'; import { jwt } from '../../../plugins/jwt'; import UserService from '../user.service'; +import { UserServiceFactory } from '../auth.factory'; describe('POST /api/auth/login', () => { let userService: UserService; @@ -9,7 +10,7 @@ describe('POST /api/auth/login', () => { const userPassword = '1234'; beforeAll(async () => { - userService = new UserService(); + userService = UserServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/auth/__test__/logout.test.ts b/src/modules/auth/__test__/logout.test.ts index ecb2ed3..69e56e0 100644 --- a/src/modules/auth/__test__/logout.test.ts +++ b/src/modules/auth/__test__/logout.test.ts @@ -1,14 +1,15 @@ import { prisma } from '../../../plugins/prisma'; import UserService from '../user.service'; import AuthService from '../auth.service'; +import { AuthServiceFactory, UserServiceFactory } from '../auth.factory'; describe('POST /api/auth/logout', () => { let userService: UserService; let authService: AuthService; beforeAll(async () => { - userService = new UserService(); - authService = new AuthService(); + userService = UserServiceFactory.make(); + authService = AuthServiceFactory.make(); }); beforeEach(async () => { diff --git a/src/modules/auth/__test__/refresh.test.ts b/src/modules/auth/__test__/refresh.test.ts index bfdf2dd..175551b 100644 --- a/src/modules/auth/__test__/refresh.test.ts +++ b/src/modules/auth/__test__/refresh.test.ts @@ -5,6 +5,7 @@ import { jwt } from '../../../plugins/jwt'; import TimeUtil from '../../../utils/time'; import AuthService from '../auth.service'; import UserService from '../user.service'; +import { AuthServiceFactory, UserServiceFactory } from '../auth.factory'; describe('POST /api/auth/refresh', () => { let authService: AuthService; @@ -13,8 +14,8 @@ describe('POST /api/auth/refresh', () => { let user: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/auth/__test__/register.test.ts b/src/modules/auth/__test__/register.test.ts index c922279..06a8e94 100644 --- a/src/modules/auth/__test__/register.test.ts +++ b/src/modules/auth/__test__/register.test.ts @@ -1,11 +1,12 @@ import { prisma } from '../../../plugins/prisma'; +import { UserServiceFactory } from '../auth.factory'; import UserService from '../user.service'; describe('POST /api/auth/register', () => { let userService: UserService; beforeAll(async () => { - userService = new UserService(); + userService = UserServiceFactory.make(); }); beforeEach(async () => { diff --git a/src/modules/auth/__test__/user.test.ts b/src/modules/auth/__test__/user.test.ts index f7f944e..cc2e8a0 100644 --- a/src/modules/auth/__test__/user.test.ts +++ b/src/modules/auth/__test__/user.test.ts @@ -4,6 +4,7 @@ import TimeUtil from '../../../utils/time'; import UserService from '../user.service'; import AuthService from '../auth.service'; import { v4 } from 'uuid'; +import { AuthServiceFactory, UserServiceFactory } from '../auth.factory'; describe('GET /api/auth/user', () => { let userService: UserService; @@ -12,8 +13,8 @@ describe('GET /api/auth/user', () => { let user: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/auth/auth.factory.ts b/src/modules/auth/auth.factory.ts new file mode 100644 index 0000000..6325126 --- /dev/null +++ b/src/modules/auth/auth.factory.ts @@ -0,0 +1,21 @@ +import AuthController from './auth.controller'; +import AuthService from './auth.service'; +import UserService from './user.service'; + +export class UserServiceFactory { + static make() { + return new UserService(); + } +} + +export class AuthServiceFactory { + static make() { + return new AuthService(); + } +} + +export class AuthControllerFactory { + static make() { + return new AuthController(AuthServiceFactory.make(), UserServiceFactory.make()); + } +} diff --git a/src/modules/auth/auth.route.ts b/src/modules/auth/auth.route.ts index 1f1906c..19c46e0 100644 --- a/src/modules/auth/auth.route.ts +++ b/src/modules/auth/auth.route.ts @@ -1,10 +1,8 @@ import { FastifyInstance } from 'fastify'; -import AuthController from './auth.controller'; -import AuthService from './auth.service'; -import UserService from './user.service'; +import { AuthControllerFactory } from './auth.factory'; export default async (fastify: FastifyInstance) => { - const authController = new AuthController(new AuthService(), new UserService()); + const authController = AuthControllerFactory.make(); fastify.post( '/register', diff --git a/src/modules/item/__test__/item.read.test.ts b/src/modules/item/__test__/item.read.test.ts index b60fd2e..c6c863f 100644 --- a/src/modules/item/__test__/item.read.test.ts +++ b/src/modules/item/__test__/item.read.test.ts @@ -5,6 +5,11 @@ import AuthService from '../../auth/auth.service'; import BlobService from '../blob/blob.service'; import DocsService from '../docs/docs.service'; import ShortcutService from '../shortcut/shortcut.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { BlobServiceFactory } from '../blob/blob.factory'; +import { DocsServiceFactory } from '../docs/docs.factory'; +import { ShortcutServiceFactory } from '../shortcut/shortcut.factory'; describe('GET /api/item/:parentId', () => { let userService: UserService; @@ -18,12 +23,12 @@ describe('GET /api/item/:parentId', () => { let otherUser: User; beforeAll(async () => { - userService = new UserService(); - folderService = new FolderService(); - authService = new AuthService(); - blobService = new BlobService(); - docsService = new DocsService(); - shortcutService = new ShortcutService(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + authService = AuthServiceFactory.make(); + blobService = BlobServiceFactory.make(); + docsService = DocsServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/__test__/item.root.test.ts b/src/modules/item/__test__/item.root.test.ts index 79f0283..a19a8b0 100644 --- a/src/modules/item/__test__/item.root.test.ts +++ b/src/modules/item/__test__/item.root.test.ts @@ -5,6 +5,11 @@ import AuthService from '../../auth/auth.service'; import BlobService from '../blob/blob.service'; import DocsService from '../docs/docs.service'; import ShortcutService from '../shortcut/shortcut.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { BlobServiceFactory } from '../blob/blob.factory'; +import { DocsServiceFactory } from '../docs/docs.factory'; +import { ShortcutServiceFactory } from '../shortcut/shortcut.factory'; describe('GET /api/item', () => { let userService: UserService; @@ -17,12 +22,12 @@ describe('GET /api/item', () => { let user: User; beforeAll(async () => { - userService = new UserService(); - folderService = new FolderService(); - authService = new AuthService(); - blobService = new BlobService(); - docsService = new DocsService(); - shortcutService = new ShortcutService(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + authService = AuthServiceFactory.make(); + blobService = BlobServiceFactory.make(); + docsService = DocsServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/__test__/item.service.test.ts b/src/modules/item/__test__/item.service.test.ts index 3aadc24..847cbb5 100644 --- a/src/modules/item/__test__/item.service.test.ts +++ b/src/modules/item/__test__/item.service.test.ts @@ -5,6 +5,12 @@ import FolderService from '../folder/folder.service'; import SharingService from '../sharing/sharing.service'; import BlobService from '../blob/blob.service'; import ShortcutService from '../shortcut/shortcut.service'; +import { UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { BlobServiceFactory } from '../blob/blob.factory'; +import { ShortcutServiceFactory } from '../shortcut/shortcut.factory'; +import { SharingServiceFactory } from '../sharing/sharing.factory'; +import { ItemServiceFactory } from '../item.factory'; describe('ItemService', () => { let itemService: ItemService; @@ -18,12 +24,12 @@ describe('ItemService', () => { let otherUser: User; beforeAll(async () => { - itemService = new ItemService(); - userService = new UserService(); - folderService = new FolderService(); - sharingService = new SharingService(itemService); - blobService = new BlobService(); - shortcutService = new ShortcutService(); + itemService = ItemServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + sharingService = SharingServiceFactory.make(); + blobService = BlobServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', @@ -149,7 +155,7 @@ describe('ItemService', () => { user.id, ); - const blob1 = await blobService.createBlob({ + await blobService.createBlob({ mimeType: 'text/plain', name: 'test1.txt', ownerId: user.id, @@ -157,13 +163,14 @@ describe('ItemService', () => { blobUrl: 'https://example.com/test1.txt', }); - await blobService.createBlob({ + const blob2 = await blobService.createBlob({ mimeType: 'text/plain', name: 'test2.txt', ownerId: user.id, parentId: folder.id, blobUrl: 'https://example.com/test2.txt', }); + await sharingService.deleteSharing({ itemId: blob2.id, userId: otherUser.id }, user.id); const folder1 = await folderService.createFolder({ name: 'Folder1', @@ -171,12 +178,14 @@ describe('ItemService', () => { ownerId: user.id, parentId: folder.id, }); - await folderService.createFolder({ + const folder2 = await folderService.createFolder({ name: 'Folder2', color: '#111111', ownerId: user.id, parentId: folder.id, }); + await sharingService.deleteSharing({ itemId: folder2.id, userId: otherUser.id }, user.id); + const folder3 = await folderService.createFolder({ name: 'Folder3', color: '#987654', @@ -184,51 +193,20 @@ describe('ItemService', () => { parentId: folder.id, }); - await sharingService.createSharing( - { - itemId: blob1.id, - userId: otherUser.id, - }, - user.id, - ); - - await sharingService.createSharing( - { - itemId: folder1.id, - userId: otherUser.id, - }, - user.id, - ); - - await sharingService.createSharing( - { - itemId: folder3.id, - userId: otherUser.id, - }, - user.id, - ); - - const shortcut1 = await shortcutService.createShortcut({ + await shortcutService.createShortcut({ name: 'Shortcut1', ownerId: user.id, linkedItemId: folder1.id, parentId: folder.id, }); - await shortcutService.createShortcut({ + const shortcut2 = await shortcutService.createShortcut({ name: 'Shortcut2', ownerId: user.id, linkedItemId: folder3.id, parentId: folder.id, }); - - await sharingService.createSharing( - { - itemId: shortcut1.id, - userId: otherUser.id, - }, - user.id, - ); + await sharingService.deleteSharing({ itemId: shortcut2.id, userId: otherUser.id }, user.id); const itemsOwner = await itemService.getAllOwnedAndSharredItemsByParentIdAndUserId( user.id, @@ -239,7 +217,7 @@ describe('ItemService', () => { folder.id, ); - const expectedOwner = [ + expect(itemsOwner).toEqual([ { id: expect.any(Number), name: 'test1.txt', @@ -331,8 +309,8 @@ describe('ItemService', () => { updatedAt: expect.any(Date), isStarred: false, }, - ]; - const expectedSharredUser = [ + ]); + expect(itemsSharredUser).toEqual([ { id: expect.any(Number), name: 'test1.txt', @@ -385,10 +363,7 @@ describe('ItemService', () => { updatedAt: expect.any(Date), isStarred: false, }, - ]; - - expect(itemsOwner).toEqual(expectedOwner); - expect(itemsSharredUser).toEqual(expectedSharredUser); + ]); }); it('should return empty array, when no items are found', async () => { diff --git a/src/modules/item/__test__/item.shared.test.ts b/src/modules/item/__test__/item.shared.test.ts index e39bee1..ef21a9d 100644 --- a/src/modules/item/__test__/item.shared.test.ts +++ b/src/modules/item/__test__/item.shared.test.ts @@ -6,7 +6,12 @@ import BlobService from '../blob/blob.service'; import DocsService from '../docs/docs.service'; import ShortcutService from '../shortcut/shortcut.service'; import SharingService from '../sharing/sharing.service'; -import ItemService from '../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { BlobServiceFactory } from '../blob/blob.factory'; +import { DocsServiceFactory } from '../docs/docs.factory'; +import { ShortcutServiceFactory } from '../shortcut/shortcut.factory'; +import { SharingServiceFactory } from '../sharing/sharing.factory'; describe('GET /api/item/shared', () => { let userService: UserService; @@ -21,13 +26,13 @@ describe('GET /api/item/shared', () => { let otherUser: User; beforeAll(async () => { - userService = new UserService(); - folderService = new FolderService(); - authService = new AuthService(); - blobService = new BlobService(); - docsService = new DocsService(); - shortcutService = new ShortcutService(); - sharingService = new SharingService(new ItemService()); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + authService = AuthServiceFactory.make(); + blobService = BlobServiceFactory.make(); + docsService = DocsServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', @@ -65,55 +70,27 @@ describe('GET /api/item/shared', () => { parentId: folder1.id, blobUrl: 'https://example.com/test1.txt', }); - await sharingService.createSharing( - { - itemId: blob.id, - userId: user.id, - }, - otherUser.id, - ); - const folder2 = await folderService.createFolder({ + await folderService.createFolder({ name: 'Folder2', color: '#987654', ownerId: otherUser.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: folder2.id, - userId: user.id, - }, - otherUser.id, - ); - const docs = await docsService.createDocs({ + await docsService.createDocs({ name: 'Docs1', text: 'Docs1 text', ownerId: otherUser.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: docs.id, - userId: user.id, - }, - otherUser.id, - ); - const shortcut = await shortcutService.createShortcut({ + await shortcutService.createShortcut({ name: 'Shortcut', ownerId: otherUser.id, linkedItemId: blob.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: shortcut.id, - userId: user.id, - }, - otherUser.id, - ); const response = await global.fastify.inject({ method: 'GET', @@ -123,8 +100,6 @@ describe('GET /api/item/shared', () => { }, }); - console.log(response.json()); - expect(response.statusCode).toBe(200); expect(response.json()).toEqual([ { @@ -211,55 +186,27 @@ describe('GET /api/item/shared', () => { parentId: folder1.id, blobUrl: 'https://example.com/test1.txt', }); - await sharingService.createSharing( - { - itemId: blob.id, - userId: user.id, - }, - otherUser.id, - ); - const folder2 = await folderService.createFolder({ + await folderService.createFolder({ name: 'Folder2', color: '#987654', ownerId: otherUser.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: folder2.id, - userId: user.id, - }, - otherUser.id, - ); - const docs = await docsService.createDocs({ + await docsService.createDocs({ name: 'Docs1', text: 'Docs1 text', ownerId: otherUser.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: docs.id, - userId: user.id, - }, - otherUser.id, - ); - const shortcut = await shortcutService.createShortcut({ + await shortcutService.createShortcut({ name: 'Shortcut', ownerId: otherUser.id, linkedItemId: blob.id, parentId: folder1.id, }); - await sharingService.createSharing( - { - itemId: shortcut.id, - userId: user.id, - }, - otherUser.id, - ); const response = await global.fastify.inject({ method: 'GET', diff --git a/src/modules/item/__test__/item.sharings.test.ts b/src/modules/item/__test__/item.sharings.test.ts index dbe8dd6..90971e7 100644 --- a/src/modules/item/__test__/item.sharings.test.ts +++ b/src/modules/item/__test__/item.sharings.test.ts @@ -3,7 +3,9 @@ import UserService from '../../auth/user.service'; import FolderService from '../folder/folder.service'; import AuthService from '../../auth/auth.service'; import SharingService from '../sharing/sharing.service'; -import ItemService from '../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { SharingServiceFactory } from '../sharing/sharing.factory'; describe('GET /api/item/:id/sharings', () => { let userService: UserService; @@ -15,10 +17,10 @@ describe('GET /api/item/:id/sharings', () => { let otherUser: User; beforeAll(async () => { - userService = new UserService(); - folderService = new FolderService(); - authService = new AuthService(); - sharingService = new SharingService(new ItemService()); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + authService = AuthServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/__test__/item.starred.test.ts b/src/modules/item/__test__/item.starred.test.ts index 7b04dd2..a4485e5 100644 --- a/src/modules/item/__test__/item.starred.test.ts +++ b/src/modules/item/__test__/item.starred.test.ts @@ -5,7 +5,11 @@ import StarredService from '../starred/starred.service'; import FolderService from '../folder/folder.service'; import BlobService from '../blob/blob.service'; import SharingService from '../sharing/sharing.service'; -import ItemService from '../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder/folder.factory'; +import { BlobServiceFactory } from '../blob/blob.factory'; +import { SharingServiceFactory } from '../sharing/sharing.factory'; +import { StarredServiceFactory } from '../starred/starred.factory'; describe('GET /api/item/starred', () => { let userService: UserService; @@ -19,12 +23,12 @@ describe('GET /api/item/starred', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - starredService = new StarredService(); - folderService = new FolderService(); - blobService = new BlobService(); - sharingService = new SharingService(new ItemService()); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + starredService = StarredServiceFactory.make(); + folderService = FolderServiceFactory.make(); + blobService = BlobServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/__test__/add.test.ts b/src/modules/item/blob/__test__/add.test.ts index f8b0003..cda1a1a 100644 --- a/src/modules/item/blob/__test__/add.test.ts +++ b/src/modules/item/blob/__test__/add.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ItemServiceFactory } from '../../item.factory'; describe('POST /api/blob', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('POST /api/blob', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - itemService = new ItemService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + itemService = ItemServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/__test__/blob.service.test.ts b/src/modules/item/blob/__test__/blob.service.test.ts index 0c779ad..f757a22 100644 --- a/src/modules/item/blob/__test__/blob.service.test.ts +++ b/src/modules/item/blob/__test__/blob.service.test.ts @@ -4,6 +4,8 @@ import BlobService from '../../blob/blob.service'; import { FastifyRequest } from 'fastify'; import AuthService from '../../../auth/auth.service'; import { HandleUploadBody } from '@vercel/blob/client'; +import { BlobServiceFactory } from '../blob.factory'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; describe('BlobService', () => { let blobService: BlobService; @@ -13,9 +15,9 @@ describe('BlobService', () => { let user: User; beforeAll(async () => { - blobService = new BlobService(); - userService = new UserService(); - authService = new AuthService(); + blobService = BlobServiceFactory.make(); + userService = UserServiceFactory.make(); + authService = AuthServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/__test__/delete.test.ts b/src/modules/item/blob/__test__/delete.test.ts index fe1bfac..ca67346 100644 --- a/src/modules/item/blob/__test__/delete.test.ts +++ b/src/modules/item/blob/__test__/delete.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import BlobService from '../blob.service'; import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { BlobServiceFactory } from '../blob.factory'; +import { ItemServiceFactory } from '../../item.factory'; describe('DELETE /api/blob/:id', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('DELETE /api/blob/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - blobService = new BlobService(); - itemService = new ItemService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + blobService = BlobServiceFactory.make(); + itemService = ItemServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/__test__/edit.test.ts b/src/modules/item/blob/__test__/edit.test.ts index 930c324..b59e000 100644 --- a/src/modules/item/blob/__test__/edit.test.ts +++ b/src/modules/item/blob/__test__/edit.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import BlobService from '../blob.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { BlobServiceFactory } from '../blob.factory'; describe('PUT /api/blob', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('PUT /api/blob', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - blobService = new BlobService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + blobService = BlobServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/__test__/read.test.ts b/src/modules/item/blob/__test__/read.test.ts index ecac735..489cce2 100644 --- a/src/modules/item/blob/__test__/read.test.ts +++ b/src/modules/item/blob/__test__/read.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import BlobService from '../blob.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { BlobServiceFactory } from '../blob.factory'; describe('GET /api/blob/:id', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('GET /api/blob/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - blobService = new BlobService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + blobService = BlobServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/blob/blob.controller.ts b/src/modules/item/blob/blob.controller.ts index 5822186..20b9e85 100644 --- a/src/modules/item/blob/blob.controller.ts +++ b/src/modules/item/blob/blob.controller.ts @@ -2,6 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { UploadInput, ReadInput, EditInput, DeleteInput } from './blob.schema'; import BlobService from './blob.service'; import AccessService from '../sharing/access.service'; +import { ItemEventType, triggerItemEvent } from '../item.event'; export default class BlobController { private blobService: BlobService; @@ -51,6 +52,8 @@ export default class BlobController { const updatedBlob = await this.blobService.updateBlob(request.body); + triggerItemEvent(updatedBlob, ItemEventType.UPDATE); + return reply.code(200).send(updatedBlob); } catch (e) { if (e instanceof Error) { @@ -94,13 +97,15 @@ export default class BlobController { throw new Error('Unauthorized'); } - await this.blobService.createBlob({ + const createdBlob = await this.blobService.createBlob({ name: blob.pathname, mimeType: blob.contentType, blobUrl: blob.url, ownerId: tokenPayloadObject.ownerId, parentId: tokenPayloadObject.parentId ?? null, }); + + triggerItemEvent(createdBlob, ItemEventType.UPDATE); } catch (e) { request.log.error(e); await this.blobService.deleteBlobByUrl(blob.url); @@ -163,6 +168,8 @@ export default class BlobController { await this.blobService.deleteBlobByItemId(blob.id); + triggerItemEvent(blob, ItemEventType.DELETE); + return reply.code(204).send(); } catch (e) { if (e instanceof Error) { diff --git a/src/modules/item/blob/blob.factory.ts b/src/modules/item/blob/blob.factory.ts new file mode 100644 index 0000000..fd1d4b7 --- /dev/null +++ b/src/modules/item/blob/blob.factory.ts @@ -0,0 +1,15 @@ +import { AccessServiceFactory, SharingServiceFactory } from '../sharing/sharing.factory'; +import BlobController from './blob.controller'; +import BlobService from './blob.service'; + +export class BlobServiceFactory { + static make() { + return new BlobService(SharingServiceFactory.make()); + } +} + +export class BlobControllerFactory { + static make() { + return new BlobController(BlobServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/blob/blob.route.ts b/src/modules/item/blob/blob.route.ts index c68e31e..39b1915 100644 --- a/src/modules/item/blob/blob.route.ts +++ b/src/modules/item/blob/blob.route.ts @@ -1,16 +1,8 @@ import { FastifyInstance } from 'fastify'; -import BlobController from './blob.controller'; -import BlobService from './blob.service'; -import SharingService from '../sharing/sharing.service'; -import ItemService from '../item.service'; -import AccessService from '../sharing/access.service'; +import { BlobControllerFactory } from './blob.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const blobController = new BlobController( - new BlobService(), - new AccessService(itemService, new SharingService(itemService)), - ); + const blobController = BlobControllerFactory.make(); fastify.get( '/:id', diff --git a/src/modules/item/blob/blob.service.ts b/src/modules/item/blob/blob.service.ts index e98bd9a..861f4f1 100644 --- a/src/modules/item/blob/blob.service.ts +++ b/src/modules/item/blob/blob.service.ts @@ -4,6 +4,7 @@ import { accessTokenPayload, jwt } from '../../../plugins/jwt'; import { FastifyRequest } from 'fastify'; import { prisma } from '../../../plugins/prisma'; import { Blob, CreateBlob, UpdateBlob, ItemBlob } from './blob.schema'; +import SharingService from '../sharing/sharing.service'; type OnUploadCompletedCallback = (body: { blob: PutBlobResult; @@ -26,6 +27,12 @@ type BlobUploadedCompletedResponse = { }; export default class BlobService { + private sharingService: SharingService; + + constructor(sharingService: SharingService) { + this.sharingService = sharingService; + } + private formatItemBlob(itemBlob: ItemBlob): Blob { return { blobUrl: itemBlob.blobUrl, @@ -68,6 +75,10 @@ export default class BlobService { }, }); + if (input.parentId) { + await this.sharingService.syncSharingsByItemId(input.parentId, itemBlob.item.id); + } + return this.formatItemBlob(itemBlob); } diff --git a/src/modules/item/docs/__test__/add.test.ts b/src/modules/item/docs/__test__/add.test.ts index f5c0959..b357f78 100644 --- a/src/modules/item/docs/__test__/add.test.ts +++ b/src/modules/item/docs/__test__/add.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import DocsService from '../docs.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { DocsServiceFactory } from '../docs.factory'; describe('POST /api/docs', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('POST /api/docs', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - docsService = new DocsService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + docsService = DocsServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/docs/__test__/delete.test.ts b/src/modules/item/docs/__test__/delete.test.ts index 93a32a8..507263b 100644 --- a/src/modules/item/docs/__test__/delete.test.ts +++ b/src/modules/item/docs/__test__/delete.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import DocsService from '../docs.service'; import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { DocsServiceFactory } from '../docs.factory'; +import { ItemServiceFactory } from '../../item.factory'; describe('DELETE /api/docs/:id', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('DELETE /api/docs/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - docsService = new DocsService(); - itemService = new ItemService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + docsService = DocsServiceFactory.make(); + itemService = ItemServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/docs/__test__/edit.test.ts b/src/modules/item/docs/__test__/edit.test.ts index 3938b1c..d6b89c0 100644 --- a/src/modules/item/docs/__test__/edit.test.ts +++ b/src/modules/item/docs/__test__/edit.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import DocsService from '../docs.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { DocsServiceFactory } from '../docs.factory'; describe('PUT /api/docs', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('PUT /api/docs', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - docsService = new DocsService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + docsService = DocsServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/docs/__test__/read.test.ts b/src/modules/item/docs/__test__/read.test.ts index 7676680..649586d 100644 --- a/src/modules/item/docs/__test__/read.test.ts +++ b/src/modules/item/docs/__test__/read.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import DocsService from '../docs.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { DocsServiceFactory } from '../docs.factory'; describe('GET /api/docs/:id', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('GET /api/docs/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - docsService = new DocsService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + docsService = DocsServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/docs/docs.controller.ts b/src/modules/item/docs/docs.controller.ts index 74abebd..4f17da7 100644 --- a/src/modules/item/docs/docs.controller.ts +++ b/src/modules/item/docs/docs.controller.ts @@ -2,6 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { ReadInput, EditInput, AddInput, DeleteInput } from './docs.schema'; import DocsService from './docs.service'; import AccessService from '../sharing/access.service'; +import { ItemEventType, triggerItemEvent } from '../item.event'; export default class DocsController { private docsService: DocsService; @@ -51,6 +52,8 @@ export default class DocsController { const updatedDocs = await this.docsService.updateDocs(request.body); + triggerItemEvent(updatedDocs, ItemEventType.UPDATE); + return reply.code(200).send(updatedDocs); } catch (e) { if (e instanceof Error) { @@ -84,6 +87,8 @@ export default class DocsController { parentId: request.body.parentId ?? null, }); + triggerItemEvent(docs, ItemEventType.UPDATE); + return reply.code(200).send(docs); } catch (e) { /* istanbul ignore next */ @@ -105,6 +110,9 @@ export default class DocsController { } await this.docsService.deleteDocsByItemId(docs.id); + + triggerItemEvent(docs, ItemEventType.DELETE); + return reply.code(204).send(); } catch (e) { if (e instanceof Error) { diff --git a/src/modules/item/docs/docs.factory.ts b/src/modules/item/docs/docs.factory.ts new file mode 100644 index 0000000..47d3153 --- /dev/null +++ b/src/modules/item/docs/docs.factory.ts @@ -0,0 +1,15 @@ +import { AccessServiceFactory, SharingServiceFactory } from '../sharing/sharing.factory'; +import DocsController from './docs.controller'; +import DocsService from './docs.service'; + +export class DocsServiceFactory { + static make() { + return new DocsService(SharingServiceFactory.make()); + } +} + +export class DocsControllerFactory { + static make() { + return new DocsController(DocsServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/docs/docs.route.ts b/src/modules/item/docs/docs.route.ts index 042b411..d6e1718 100644 --- a/src/modules/item/docs/docs.route.ts +++ b/src/modules/item/docs/docs.route.ts @@ -1,17 +1,8 @@ import { FastifyInstance } from 'fastify'; -import DocsController from './docs.controller'; -import DocsService from './docs.service'; -import AccessService from '../sharing/access.service'; -import ItemService from '../item.service'; -import SharingService from '../sharing/sharing.service'; +import { DocsControllerFactory } from './docs.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const docsService = new DocsService(); - const docsController = new DocsController( - docsService, - new AccessService(itemService, new SharingService(itemService)), - ); + const docsController = DocsControllerFactory.make(); fastify.get( '/:id', diff --git a/src/modules/item/docs/docs.service.ts b/src/modules/item/docs/docs.service.ts index d65fbef..81305ff 100644 --- a/src/modules/item/docs/docs.service.ts +++ b/src/modules/item/docs/docs.service.ts @@ -1,7 +1,14 @@ import { prisma } from '../../../plugins/prisma'; +import SharingService from '../sharing/sharing.service'; import { Docs, AddDocs, UpdateDocs, ItemDocs } from './docs.schema'; export default class DocsService { + private sharingService: SharingService; + + constructor(sharingService: SharingService) { + this.sharingService = sharingService; + } + public async createDocs(input: AddDocs): Promise { const itemDocs = await prisma.itemDocs.create({ data: { @@ -20,6 +27,10 @@ export default class DocsService { }, }); + if (input.parentId) { + await this.sharingService.syncSharingsByItemId(input.parentId, itemDocs.item.id); + } + return this.formatitemDocs(itemDocs); } diff --git a/src/modules/item/folder/__test__/add.test.ts b/src/modules/item/folder/__test__/add.test.ts index 1e78862..6755498 100644 --- a/src/modules/item/folder/__test__/add.test.ts +++ b/src/modules/item/folder/__test__/add.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import FolderService from '../folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder.factory'; describe('POST /api/folder', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('POST /api/folder', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/folder/__test__/delete.test.ts b/src/modules/item/folder/__test__/delete.test.ts index 927c5c9..a2ad5c9 100644 --- a/src/modules/item/folder/__test__/delete.test.ts +++ b/src/modules/item/folder/__test__/delete.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import FolderService from '../folder.service'; import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder.factory'; +import { ItemServiceFactory } from '../../item.factory'; describe('DELETE /api/folder/:id', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('DELETE /api/folder/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); - itemService = new ItemService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + itemService = ItemServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/folder/__test__/edit.test.ts b/src/modules/item/folder/__test__/edit.test.ts index 5a381be..da0c898 100644 --- a/src/modules/item/folder/__test__/edit.test.ts +++ b/src/modules/item/folder/__test__/edit.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import FolderService from '../folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder.factory'; describe('PUT /api/folder', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('PUT /api/folder', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/folder/__test__/read.test.ts b/src/modules/item/folder/__test__/read.test.ts index c9dc3ca..27ffd11 100644 --- a/src/modules/item/folder/__test__/read.test.ts +++ b/src/modules/item/folder/__test__/read.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import FolderService from '../folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../folder.factory'; describe('GET /api/folder/:id', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('GET /api/folder/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/folder/folder.controller.ts b/src/modules/item/folder/folder.controller.ts index 1a51753..830a685 100644 --- a/src/modules/item/folder/folder.controller.ts +++ b/src/modules/item/folder/folder.controller.ts @@ -2,6 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { ReadInput, EditInput, AddInput, DeleteInput } from './folder.schema'; import FolderService from './folder.service'; import AccessService from '../sharing/access.service'; +import { ItemEventType, triggerItemEvent } from '../item.event'; export default class FolderController { private folderService: FolderService; @@ -59,6 +60,8 @@ export default class FolderController { const updatedFolder = await this.folderService.updateFolder(request.body); + triggerItemEvent(updatedFolder, ItemEventType.UPDATE); + return reply.code(200).send(updatedFolder); } catch (e) { if (e instanceof Error) { @@ -92,6 +95,8 @@ export default class FolderController { parentId: request.body.parentId ?? null, }); + triggerItemEvent(folder, ItemEventType.UPDATE); + return reply.code(200).send(folder); } catch (e) { /* istanbul ignore next */ @@ -113,6 +118,9 @@ export default class FolderController { } await this.folderService.deleteFolderByItemId(folder.id); + + triggerItemEvent(folder, ItemEventType.DELETE); + return reply.code(204).send(); } catch (e) { if (e instanceof Error) { diff --git a/src/modules/item/folder/folder.factory.ts b/src/modules/item/folder/folder.factory.ts new file mode 100644 index 0000000..157109d --- /dev/null +++ b/src/modules/item/folder/folder.factory.ts @@ -0,0 +1,15 @@ +import { AccessServiceFactory, SharingServiceFactory } from '../sharing/sharing.factory'; +import FolderController from './folder.controller'; +import FolderService from './folder.service'; + +export class FolderServiceFactory { + static make() { + return new FolderService(SharingServiceFactory.make()); + } +} + +export class FolderControllerFactory { + static make() { + return new FolderController(FolderServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/folder/folder.route.ts b/src/modules/item/folder/folder.route.ts index 422d88d..5311461 100644 --- a/src/modules/item/folder/folder.route.ts +++ b/src/modules/item/folder/folder.route.ts @@ -1,17 +1,8 @@ import { FastifyInstance } from 'fastify'; -import FolderController from './folder.controller'; -import FolderService from './folder.service'; -import AccessService from '../sharing/access.service'; -import ItemService from '../item.service'; -import SharingService from '../sharing/sharing.service'; +import { FolderControllerFactory } from './folder.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const folderService = new FolderService(); - const folderController = new FolderController( - folderService, - new AccessService(itemService, new SharingService(itemService)), - ); + const folderController = FolderControllerFactory.make(); fastify.get( '/:id', diff --git a/src/modules/item/folder/folder.service.ts b/src/modules/item/folder/folder.service.ts index 5dd672d..20b494a 100644 --- a/src/modules/item/folder/folder.service.ts +++ b/src/modules/item/folder/folder.service.ts @@ -1,7 +1,14 @@ import { prisma } from '../../../plugins/prisma'; +import SharingService from '../sharing/sharing.service'; import { Folder, AddFolder, UpdateFolder, ItemFolder } from './folder.schema'; export default class FolderService { + private sharingService: SharingService; + + constructor(sharingService: SharingService) { + this.sharingService = sharingService; + } + public async createFolder(input: AddFolder): Promise { const itemFolder = await prisma.itemFolder.create({ data: { @@ -20,6 +27,10 @@ export default class FolderService { }, }); + if (input.parentId) { + await this.sharingService.syncSharingsByItemId(input.parentId, itemFolder.item.id); + } + return this.formatItemFolder(itemFolder); } diff --git a/src/modules/item/item.event.ts b/src/modules/item/item.event.ts new file mode 100644 index 0000000..2721a55 --- /dev/null +++ b/src/modules/item/item.event.ts @@ -0,0 +1,29 @@ +import { pusher } from '../../plugins/pusher'; +import { Item } from './item.schema'; + +export enum ItemEventType { + UPDATE = 'update', + DELETE = 'delete', +} + +/* istanbul ignore next */ +const getItemParentChannel = (item: Item): string => { + if (!item.parentId) { + return `browser-root-${item.ownerId}`; + } + + return `browser-folder-${item.parentId}`; +}; + +/* istanbul ignore next */ +export const triggerItemEvent = async (item: Item, type: ItemEventType): Promise => { + if (process.env.NODE_ENV === 'test') { + return; + } + + /* istanbul ignore next */ + const channelName = getItemParentChannel(item); + + /* istanbul ignore next */ + await pusher.trigger(channelName, type, item); +}; diff --git a/src/modules/item/item.factory.ts b/src/modules/item/item.factory.ts new file mode 100644 index 0000000..ec64c16 --- /dev/null +++ b/src/modules/item/item.factory.ts @@ -0,0 +1,15 @@ +import ItemController from './item.controller'; +import ItemService from './item.service'; +import { AccessServiceFactory } from './sharing/sharing.factory'; + +export class ItemServiceFactory { + static make() { + return new ItemService(); + } +} + +export class ItemControllerFactory { + static make() { + return new ItemController(ItemServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/item.route.ts b/src/modules/item/item.route.ts index ba124b3..689ae4d 100644 --- a/src/modules/item/item.route.ts +++ b/src/modules/item/item.route.ts @@ -1,15 +1,8 @@ import { FastifyInstance } from 'fastify'; -import ItemController from './item.controller'; -import ItemService from './item.service'; -import AccessService from './sharing/access.service'; -import SharingService from './sharing/sharing.service'; +import { ItemControllerFactory } from './item.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const itemController = new ItemController( - itemService, - new AccessService(itemService, new SharingService(itemService)), - ); + const itemController = ItemControllerFactory.make(); fastify.get( '/starred', diff --git a/src/modules/item/item.service.ts b/src/modules/item/item.service.ts index 719da98..4ae2e1a 100644 --- a/src/modules/item/item.service.ts +++ b/src/modules/item/item.service.ts @@ -149,6 +149,9 @@ export default class ItemService { userId: userId, }, }, + ownerId: { + not: userId, + }, }, include: { ItemBlob: true, diff --git a/src/modules/item/sharing/__test__/access.service.test.ts b/src/modules/item/sharing/__test__/access.service.test.ts index 03d08ba..d1b9a84 100644 --- a/src/modules/item/sharing/__test__/access.service.test.ts +++ b/src/modules/item/sharing/__test__/access.service.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import ItemService from '../../item.service'; import AccessService from '../access.service'; import SharingService from '../sharing.service'; +import { ItemServiceFactory } from '../../item.factory'; +import { UserServiceFactory } from '../../../auth/auth.factory'; +import { AccessServiceFactory, SharingServiceFactory } from '../sharing.factory'; describe('ItemService', () => { let itemService: ItemService; @@ -14,10 +17,10 @@ describe('ItemService', () => { let otherUser: User; beforeAll(async () => { - itemService = new ItemService(); - userService = new UserService(); - sharingService = new SharingService(itemService); - accessService = new AccessService(itemService, sharingService); + itemService = ItemServiceFactory.make(); + userService = UserServiceFactory.make(); + sharingService = SharingServiceFactory.make(); + accessService = AccessServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/sharing/__test__/add.test.ts b/src/modules/item/sharing/__test__/add.test.ts index 646bae3..8a9ac03 100644 --- a/src/modules/item/sharing/__test__/add.test.ts +++ b/src/modules/item/sharing/__test__/add.test.ts @@ -4,6 +4,10 @@ import AuthService from '../../../auth/auth.service'; import ItemService from '../../item.service'; import SharingService from '../sharing.service'; import FolderService from '../../folder/folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ItemServiceFactory } from '../../item.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { SharingServiceFactory } from '../sharing.factory'; describe('POST /api/sharing', () => { let userService: UserService; @@ -16,11 +20,11 @@ describe('POST /api/sharing', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - itemService = new ItemService(); - folderService = new FolderService(); - sharingService = new SharingService(itemService); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + itemService = ItemServiceFactory.make(); + folderService = FolderServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/sharing/__test__/delete.test.ts b/src/modules/item/sharing/__test__/delete.test.ts index 82a5e69..1a6a933 100644 --- a/src/modules/item/sharing/__test__/delete.test.ts +++ b/src/modules/item/sharing/__test__/delete.test.ts @@ -4,6 +4,10 @@ import AuthService from '../../../auth/auth.service'; import ItemService from '../../item.service'; import SharingService from '../sharing.service'; import FolderService from '../../folder/folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ItemServiceFactory } from '../../item.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { SharingServiceFactory } from '../sharing.factory'; describe('DELETE /api/sharing/:id', () => { let userService: UserService; @@ -16,11 +20,11 @@ describe('DELETE /api/sharing/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - itemService = new ItemService(); - folderService = new FolderService(); - sharingService = new SharingService(itemService); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + itemService = ItemServiceFactory.make(); + folderService = FolderServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', @@ -116,13 +120,6 @@ describe('DELETE /api/sharing/:id', () => { parentId: folder.id, color: 'red', }); - await sharingService.createSharing( - { - itemId: subFolder.id, - userId: user.id, - }, - user.id, - ); const item3 = await itemService.createItem({ name: 'test3.txt', diff --git a/src/modules/item/sharing/__test__/edit.test.ts b/src/modules/item/sharing/__test__/edit.test.ts index 3a4fb46..e8bbc02 100644 --- a/src/modules/item/sharing/__test__/edit.test.ts +++ b/src/modules/item/sharing/__test__/edit.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import ItemService from '../../item.service'; import SharingService from '../sharing.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ItemServiceFactory } from '../../item.factory'; +import { SharingServiceFactory } from '../sharing.factory'; describe('PUT /api/sharing', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('PUT /api/sharing', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - itemService = new ItemService(); - sharingService = new SharingService(itemService); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + itemService = ItemServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/sharing/__test__/read.test.ts b/src/modules/item/sharing/__test__/read.test.ts index b28fc87..9e7bd69 100644 --- a/src/modules/item/sharing/__test__/read.test.ts +++ b/src/modules/item/sharing/__test__/read.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import ItemService from '../../item.service'; import SharingService from '../sharing.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ItemServiceFactory } from '../../item.factory'; +import { SharingServiceFactory } from '../sharing.factory'; describe('GET /api/sharing/:id', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('GET /api/sharing/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - itemService = new ItemService(); - sharingService = new SharingService(itemService); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + itemService = ItemServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/sharing/__test__/sharing.service.test.ts b/src/modules/item/sharing/__test__/sharing.service.test.ts new file mode 100644 index 0000000..0bff0b7 --- /dev/null +++ b/src/modules/item/sharing/__test__/sharing.service.test.ts @@ -0,0 +1,82 @@ +import { User } from '@prisma/client'; +import UserService from '../../../auth/user.service'; +import FolderService from '../../folder/folder.service'; +import SharingService from '../sharing.service'; +import BlobService from '../../blob/blob.service'; +import { UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { SharingServiceFactory } from '../sharing.factory'; +import { BlobServiceFactory } from '../../blob/blob.factory'; + +describe('SharingService', () => { + let userService: UserService; + let folderService: FolderService; + let sharingService: SharingService; + let blobService: BlobService; + + let user: User; + let otherUser: User; + + beforeAll(async () => { + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + sharingService = SharingServiceFactory.make(); + blobService = BlobServiceFactory.make(); + + user = await userService.createUser({ + name: 'Joe Biden the 1st', + email: 'joe@biden.com', + password: '1234', + }); + otherUser = await userService.createUser({ + name: 'Joe Biden the 2nd', + email: 'joe2@biden.com', + password: '4321', + }); + }); + + describe('deleteSharing()', () => { + it('should delete sharing and all child sharings', async () => { + const folder = await folderService.createFolder({ + name: 'Folder1', + color: '#123456', + ownerId: user.id, + parentId: null, + }); + await sharingService.createSharing({ itemId: folder.id, userId: otherUser.id }, user.id); + + const subFolder = await folderService.createFolder({ + name: 'Sub Folder', + color: '#7890123', + ownerId: user.id, + parentId: folder.id, + }); + + const blob = await blobService.createBlob({ + mimeType: 'text/plain', + name: 'test1.txt', + ownerId: user.id, + parentId: subFolder.id, + blobUrl: 'https://example.com/test1.txt', + }); + + await Promise.all([ + expect(sharingService.getByItemIdAndUserId(folder.id, otherUser.id)).resolves.toBeDefined(), + expect( + sharingService.getByItemIdAndUserId(subFolder.id, otherUser.id), + ).resolves.toBeDefined(), + expect(sharingService.getByItemIdAndUserId(blob.id, otherUser.id)).resolves.toBeDefined(), + ]); + + await sharingService.deleteSharing({ itemId: subFolder.id, userId: otherUser.id }, user.id); + + await Promise.all([ + expect(sharingService.getByItemIdAndUserId(folder.id, otherUser.id)).resolves.toBeDefined(), + expect( + sharingService.getByItemIdAndUserId(subFolder.id, otherUser.id), + ).rejects.toThrowError(), + expect(sharingService.getByItemIdAndUserId(blob.id, otherUser.id)).rejects.toThrowError(), + ]); + }); + }); +}); diff --git a/src/modules/item/sharing/sharing.factory.ts b/src/modules/item/sharing/sharing.factory.ts new file mode 100644 index 0000000..1d7e173 --- /dev/null +++ b/src/modules/item/sharing/sharing.factory.ts @@ -0,0 +1,27 @@ +import { UserServiceFactory } from '../../auth/auth.factory'; +import { ItemServiceFactory } from '../item.factory'; +import AccessService from './access.service'; +import SharingController from './sharing.controller'; +import SharingService from './sharing.service'; + +export class SharingServiceFactory { + static make() { + return new SharingService(ItemServiceFactory.make()); + } +} + +export class AccessServiceFactory { + static make() { + return new AccessService(ItemServiceFactory.make(), SharingServiceFactory.make()); + } +} + +export class SharingControllerFactory { + static make() { + return new SharingController( + SharingServiceFactory.make(), + AccessServiceFactory.make(), + UserServiceFactory.make(), + ); + } +} diff --git a/src/modules/item/sharing/sharing.route.ts b/src/modules/item/sharing/sharing.route.ts index b237e12..5bf1fb3 100644 --- a/src/modules/item/sharing/sharing.route.ts +++ b/src/modules/item/sharing/sharing.route.ts @@ -1,18 +1,8 @@ import { FastifyInstance } from 'fastify'; -import SharingController from './sharing.controller'; -import SharingService from './sharing.service'; -import AccessService from './access.service'; -import ItemService from '../item.service'; -import UserService from '../../auth/user.service'; +import { SharingControllerFactory } from './sharing.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const sharingService = new SharingService(itemService); - const sharingController = new SharingController( - sharingService, - new AccessService(itemService, sharingService), - new UserService(), - ); + const sharingController = SharingControllerFactory.make(); fastify.get( '/:id', diff --git a/src/modules/item/sharing/sharing.schema.ts b/src/modules/item/sharing/sharing.schema.ts index 619e14d..a6d0f98 100644 --- a/src/modules/item/sharing/sharing.schema.ts +++ b/src/modules/item/sharing/sharing.schema.ts @@ -11,6 +11,10 @@ export type UpdateSharing = { itemId: number; userId: number; } & Partial; +export type DeleteSharing = { + itemId: number; + userId: number; +}; const addSharingSchema = { $id: 'addSharingSchema', diff --git a/src/modules/item/sharing/sharing.service.ts b/src/modules/item/sharing/sharing.service.ts index 0adff34..a3f1463 100644 --- a/src/modules/item/sharing/sharing.service.ts +++ b/src/modules/item/sharing/sharing.service.ts @@ -1,6 +1,6 @@ import { prisma } from '../../../plugins/prisma'; import ItemService from '../item.service'; -import { Sharing, CreateSharing, UpdateSharing } from './sharing.schema'; +import { Sharing, CreateSharing, UpdateSharing, DeleteSharing } from './sharing.schema'; export default class SharingService { private itemService: ItemService; @@ -77,6 +77,44 @@ export default class SharingService { } } + public async deleteSharing(input: DeleteSharing, userId: number): Promise { + try { + const itemSharing = await prisma.itemSharing.findUniqueOrThrow({ + where: { + itemId_userId: { + itemId: input.itemId, + userId: input.userId, + }, + }, + }); + + const accessableItems = + await this.itemService.getAllOwnedAndSharredItemsByParentIdAndUserIdRecursively( + userId, + itemSharing.itemId, + ); + + await prisma.itemSharing.deleteMany({ + where: { + OR: [ + { + itemId: input.itemId, + userId: input.userId, + }, + { + itemId: { + in: accessableItems.map((item) => item.id), + }, + userId: itemSharing.userId, + }, + ], + }, + }); + } catch (e) { + // Nothing to do here + } + } + public async updateSharing(input: UpdateSharing): Promise { const itemSharing = await prisma.itemSharing.update({ data: { @@ -109,20 +147,47 @@ export default class SharingService { await prisma.itemSharing.deleteMany({ where: { - itemId: { - in: accessableItems.map((item) => item.id), - }, - userId: itemSharing.userId, - }, - }); - - await prisma.itemSharing.delete({ - where: { - id: id, + OR: [ + { + itemId: { + in: accessableItems.map((item) => item.id), + }, + userId: itemSharing.userId, + }, + { + id: id, + }, + ], }, }); } catch (e) { // Nothing to do here } } + + public async syncSharingsByItemId(fromItemId: number, toItemId: number) { + const fromItem = await prisma.item.findUniqueOrThrow({ + where: { + id: fromItemId, + }, + include: { + ItemSharing: true, + }, + }); + + const userIds = [fromItem.ownerId]; + fromItem.ItemSharing.forEach((sharing) => { + userIds.push(sharing.userId); + }); + + await prisma.itemSharing.createMany({ + data: userIds.map((userId) => { + return { + itemId: toItemId, + userId: userId, + }; + }), + skipDuplicates: true, + }); + } } diff --git a/src/modules/item/shortcut/__test__/add.test.ts b/src/modules/item/shortcut/__test__/add.test.ts index b2f5511..a90197f 100644 --- a/src/modules/item/shortcut/__test__/add.test.ts +++ b/src/modules/item/shortcut/__test__/add.test.ts @@ -2,6 +2,8 @@ import { User } from '@prisma/client'; import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import FolderService from '../../folder/folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; describe('POST /api/shortcut', () => { let userService: UserService; @@ -12,9 +14,9 @@ describe('POST /api/shortcut', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/shortcut/__test__/delete.test.ts b/src/modules/item/shortcut/__test__/delete.test.ts index 5315a2e..1743698 100644 --- a/src/modules/item/shortcut/__test__/delete.test.ts +++ b/src/modules/item/shortcut/__test__/delete.test.ts @@ -4,6 +4,10 @@ import AuthService from '../../../auth/auth.service'; import ShortcutService from '../shortcut.service'; import FolderService from '../../folder/folder.service'; import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ShortcutServiceFactory } from '../shortcut.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { ItemServiceFactory } from '../../item.factory'; describe('DELETE /api/shortcut/:id', () => { let userService: UserService; @@ -16,11 +20,11 @@ describe('DELETE /api/shortcut/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - shortcutService = new ShortcutService(); - folderService = new FolderService(); - itemService = new ItemService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); + folderService = FolderServiceFactory.make(); + itemService = ItemServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/shortcut/__test__/edit.test.ts b/src/modules/item/shortcut/__test__/edit.test.ts index 3522072..7b9a875 100644 --- a/src/modules/item/shortcut/__test__/edit.test.ts +++ b/src/modules/item/shortcut/__test__/edit.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import ShortcutService from '../shortcut.service'; import FolderService from '../../folder/folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ShortcutServiceFactory } from '../shortcut.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; describe('PUT /api/shortcut', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('PUT /api/shortcut', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - shortcutService = new ShortcutService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/shortcut/__test__/read.test.ts b/src/modules/item/shortcut/__test__/read.test.ts index c36e8a9..46744bc 100644 --- a/src/modules/item/shortcut/__test__/read.test.ts +++ b/src/modules/item/shortcut/__test__/read.test.ts @@ -3,6 +3,9 @@ import UserService from '../../../auth/user.service'; import AuthService from '../../../auth/auth.service'; import ShortcutService from '../shortcut.service'; import FolderService from '../../folder/folder.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { ShortcutServiceFactory } from '../shortcut.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; describe('GET /api/shortcut/:id', () => { let userService: UserService; @@ -14,10 +17,10 @@ describe('GET /api/shortcut/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - shortcutService = new ShortcutService(); - folderService = new FolderService(); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + shortcutService = ShortcutServiceFactory.make(); + folderService = FolderServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/shortcut/shortcut.controller.ts b/src/modules/item/shortcut/shortcut.controller.ts index dd9dda4..03c3fd5 100644 --- a/src/modules/item/shortcut/shortcut.controller.ts +++ b/src/modules/item/shortcut/shortcut.controller.ts @@ -2,6 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { ReadInput, EditInput, AddInput, DeleteInput } from './shortcut.schema'; import ShortcutService from './shortcut.service'; import AccessService from '../sharing/access.service'; +import { ItemEventType, triggerItemEvent } from '../item.event'; export default class ShortcutController { private shortcutService: ShortcutService; @@ -59,6 +60,8 @@ export default class ShortcutController { const updatedShortcut = await this.shortcutService.updateShortcut(request.body); + triggerItemEvent(updatedShortcut, ItemEventType.UPDATE); + return reply.code(200).send(updatedShortcut); } catch (e) { if (e instanceof Error) { @@ -92,6 +95,8 @@ export default class ShortcutController { parentId: request.body.parentId ?? null, }); + triggerItemEvent(shortcut, ItemEventType.UPDATE); + return reply.code(200).send(shortcut); } catch (e) { /* istanbul ignore next */ @@ -113,6 +118,9 @@ export default class ShortcutController { } await this.shortcutService.deleteShortcutByItemId(shortcut.id); + + triggerItemEvent(shortcut, ItemEventType.DELETE); + return reply.code(204).send(); } catch (e) { if (e instanceof Error) { diff --git a/src/modules/item/shortcut/shortcut.factory.ts b/src/modules/item/shortcut/shortcut.factory.ts new file mode 100644 index 0000000..47e2f86 --- /dev/null +++ b/src/modules/item/shortcut/shortcut.factory.ts @@ -0,0 +1,15 @@ +import { AccessServiceFactory, SharingServiceFactory } from '../sharing/sharing.factory'; +import ShortcutController from './shortcut.controller'; +import ShortcutService from './shortcut.service'; + +export class ShortcutServiceFactory { + static make() { + return new ShortcutService(SharingServiceFactory.make()); + } +} + +export class ShortcutControllerFactory { + static make() { + return new ShortcutController(ShortcutServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/shortcut/shortcut.route.ts b/src/modules/item/shortcut/shortcut.route.ts index a4e8fc3..736c200 100644 --- a/src/modules/item/shortcut/shortcut.route.ts +++ b/src/modules/item/shortcut/shortcut.route.ts @@ -1,17 +1,8 @@ import { FastifyInstance } from 'fastify'; -import ShortcutController from './shortcut.controller'; -import ShortcutService from './shortcut.service'; -import AccessService from '../sharing/access.service'; -import ItemService from '../item.service'; -import SharingService from '../sharing/sharing.service'; +import { ShortcutControllerFactory } from './shortcut.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const shortcutService = new ShortcutService(); - const shortcutController = new ShortcutController( - shortcutService, - new AccessService(itemService, new SharingService(itemService)), - ); + const shortcutController = ShortcutControllerFactory.make(); fastify.get( '/:id', diff --git a/src/modules/item/shortcut/shortcut.service.ts b/src/modules/item/shortcut/shortcut.service.ts index 9689cff..1ea8700 100644 --- a/src/modules/item/shortcut/shortcut.service.ts +++ b/src/modules/item/shortcut/shortcut.service.ts @@ -1,7 +1,14 @@ import { prisma } from '../../../plugins/prisma'; +import SharingService from '../sharing/sharing.service'; import { Shortcut, AddShortcut, UpdateShortcut, ItemShortcut } from './shortcut.schema'; export default class ShortcutService { + private sharingService: SharingService; + + constructor(sharingService: SharingService) { + this.sharingService = sharingService; + } + public async createShortcut(input: AddShortcut): Promise { const itemShortcut = await prisma.itemShortcut.create({ data: { @@ -24,6 +31,10 @@ export default class ShortcutService { }, }); + if (input.parentId) { + await this.sharingService.syncSharingsByItemId(input.parentId, itemShortcut.shortcutItem.id); + } + return this.formatItemShortcut(itemShortcut); } diff --git a/src/modules/item/starred/__test__/add.test.ts b/src/modules/item/starred/__test__/add.test.ts index 4faf127..107c589 100644 --- a/src/modules/item/starred/__test__/add.test.ts +++ b/src/modules/item/starred/__test__/add.test.ts @@ -4,7 +4,10 @@ import AuthService from '../../../auth/auth.service'; import FolderService from '../../folder/folder.service'; import StarredService from '../../starred/starred.service'; import SharingService from '../../sharing/sharing.service'; -import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { StarredServiceFactory } from '../starred.factory'; +import { SharingServiceFactory } from '../../sharing/sharing.factory'; describe('POST /api/starred', () => { let userService: UserService; @@ -17,11 +20,11 @@ describe('POST /api/starred', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - folderService = new FolderService(); - starredService = new StarredService(); - sharingService = new SharingService(new ItemService()); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + folderService = FolderServiceFactory.make(); + starredService = StarredServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/starred/__test__/delete.test.ts b/src/modules/item/starred/__test__/delete.test.ts index bade754..58d3751 100644 --- a/src/modules/item/starred/__test__/delete.test.ts +++ b/src/modules/item/starred/__test__/delete.test.ts @@ -4,7 +4,10 @@ import AuthService from '../../../auth/auth.service'; import StarredService from '../starred.service'; import FolderService from '../../folder/folder.service'; import SharingService from '../../sharing/sharing.service'; -import ItemService from '../../item.service'; +import { AuthServiceFactory, UserServiceFactory } from '../../../auth/auth.factory'; +import { StarredServiceFactory } from '../starred.factory'; +import { FolderServiceFactory } from '../../folder/folder.factory'; +import { SharingServiceFactory } from '../../sharing/sharing.factory'; describe('DELETE /api/starred/:id', () => { let userService: UserService; @@ -17,11 +20,11 @@ describe('DELETE /api/starred/:id', () => { let otherUser: User; beforeAll(async () => { - authService = new AuthService(); - userService = new UserService(); - starredService = new StarredService(); - folderService = new FolderService(); - sharingService = new SharingService(new ItemService()); + authService = AuthServiceFactory.make(); + userService = UserServiceFactory.make(); + starredService = StarredServiceFactory.make(); + folderService = FolderServiceFactory.make(); + sharingService = SharingServiceFactory.make(); user = await userService.createUser({ name: 'Joe Biden the 1st', diff --git a/src/modules/item/starred/starred.factory.ts b/src/modules/item/starred/starred.factory.ts new file mode 100644 index 0000000..e7560e1 --- /dev/null +++ b/src/modules/item/starred/starred.factory.ts @@ -0,0 +1,15 @@ +import { AccessServiceFactory } from '../sharing/sharing.factory'; +import StarredController from './starred.controller'; +import StarredService from './starred.service'; + +export class StarredServiceFactory { + static make() { + return new StarredService(); + } +} + +export class StarredControllerFactory { + static make() { + return new StarredController(StarredServiceFactory.make(), AccessServiceFactory.make()); + } +} diff --git a/src/modules/item/starred/starred.route.ts b/src/modules/item/starred/starred.route.ts index 05808e3..3bde3ba 100644 --- a/src/modules/item/starred/starred.route.ts +++ b/src/modules/item/starred/starred.route.ts @@ -1,17 +1,8 @@ import { FastifyInstance } from 'fastify'; -import StarredController from './starred.controller'; -import StarredService from './starred.service'; -import AccessService from '../sharing/access.service'; -import ItemService from '../item.service'; -import SharingService from '../sharing/sharing.service'; +import { StarredControllerFactory } from './starred.factory'; export default async (fastify: FastifyInstance) => { - const itemService = new ItemService(); - const starredService = new StarredService(); - const starredController = new StarredController( - starredService, - new AccessService(itemService, new SharingService(itemService)), - ); + const starredController = StarredControllerFactory.make(); fastify.post( '/', diff --git a/src/plugins/config.ts b/src/plugins/config.ts index ec6ed9c..06f4cd7 100644 --- a/src/plugins/config.ts +++ b/src/plugins/config.ts @@ -23,6 +23,10 @@ declare module 'fastify' { REDIS_URL: string; NODE_ENV: NODE_ENV; ALLOWED_ORIGINS: string[]; + PUSHER_APP_ID: string; + PUSHER_APP_KEY: string; + PUSHER_APP_SECRET: string; + PUSHER_APP_CLUSTER: string; }; } } @@ -83,6 +87,18 @@ export default fastifyPlugin( separator: ',', default: 'http://localhost:4321', }, + PUSHER_APP_ID: { + type: 'string', + }, + PUSHER_APP_KEY: { + type: 'string', + }, + PUSHER_APP_SECRET: { + type: 'string', + }, + PUSHER_APP_CLUSTER: { + type: 'string', + }, }, }; diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 2366e02..9c310c4 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -12,6 +12,7 @@ import jwt from './jwt'; import i18n from './i18n'; import errorHandler from './error.handler'; import plainText from './plainText'; +import pusher from './pusher'; export default fastifyPlugin(async (fastify: FastifyInstance) => { await Promise.all([ @@ -30,6 +31,9 @@ export default fastifyPlugin(async (fastify: FastifyInstance) => { fastify.config.NODE_ENV === 'local' ? /* istanbul ignore next */ fastify.register(swagger) : /* istanbul ignore next */ null, + fastify.config.NODE_ENV !== 'test' + ? /* istanbul ignore next */ fastify.register(pusher) + : /* istanbul ignore next */ null, ]); await Promise.all([fastify.register(jwt)]); diff --git a/src/plugins/pusher.ts b/src/plugins/pusher.ts new file mode 100644 index 0000000..df7d0f7 --- /dev/null +++ b/src/plugins/pusher.ts @@ -0,0 +1,28 @@ +import fastifyPlugin from 'fastify-plugin'; +import { FastifyInstance } from 'fastify'; +import Pusher from 'pusher'; + +export let pusher: Pusher; + +export default fastifyPlugin( + async (fastify: FastifyInstance) => { + if ( + !fastify.config.PUSHER_APP_SECRET && + !fastify.config.PUSHER_APP_CLUSTER && + !fastify.config.PUSHER_APP_KEY && + !fastify.config.PUSHER_APP_ID + ) { + fastify.log.fatal('Missing PUSHER ENV variables'); + throw new Error('Missing PUSHER ENV variables'); + } + + pusher = new Pusher({ + appId: fastify.config.PUSHER_APP_ID, + key: fastify.config.PUSHER_APP_KEY, + secret: fastify.config.PUSHER_APP_SECRET, + cluster: fastify.config.PUSHER_APP_CLUSTER, + useTLS: true, + }); + }, + { dependencies: ['config'] }, +);