diff --git a/src/locales/da.json b/src/locales/da.json index de57f76..b9cdace 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -99,6 +99,11 @@ "required": "userId er påkrævet", "type": "userId skal være et tal" }, + "email": { + "required": "Email er påkrævet", + "type": "Email skal være en tekst", + "format": "Email skal være af korrekt format" + }, "notFound": "Delning ikke fundet", "alreadyExists": "Delning findes allerede" }, diff --git a/src/locales/en.json b/src/locales/en.json index 7e2345f..038fef9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -99,6 +99,11 @@ "required": "userId is required", "type": "userId must be a number" }, + "email": { + "required": "Email is required", + "type": "Email must be a number", + "format": "Email must be of correct format" + }, "notFound": "Sharing not found", "alreadyExists": "Sharing already exists" }, diff --git a/src/modules/item/__test__/item.service.test.ts b/src/modules/item/__test__/item.service.test.ts index c4b542f..3aadc24 100644 --- a/src/modules/item/__test__/item.service.test.ts +++ b/src/modules/item/__test__/item.service.test.ts @@ -419,4 +419,10 @@ describe('ItemService', () => { await expect(itemService.getById(1234)).rejects.toThrow(); }); }); + + describe('getItemByIdWithSharingsAndOwner()', () => { + it("should throw error, when item doesn't exist", async () => { + await expect(itemService.getItemByIdWithSharingsAndOwner(1234)).rejects.toThrow(); + }); + }); }); diff --git a/src/modules/item/__test__/item.shared.test.ts b/src/modules/item/__test__/item.shared.test.ts new file mode 100644 index 0000000..e39bee1 --- /dev/null +++ b/src/modules/item/__test__/item.shared.test.ts @@ -0,0 +1,281 @@ +import { User } from '@prisma/client'; +import UserService from '../../auth/user.service'; +import FolderService from '../folder/folder.service'; +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 SharingService from '../sharing/sharing.service'; +import ItemService from '../item.service'; + +describe('GET /api/item/shared', () => { + let userService: UserService; + let folderService: FolderService; + let authService: AuthService; + let blobService: BlobService; + let docsService: DocsService; + let shortcutService: ShortcutService; + let sharingService: SharingService; + + let user: User; + 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()); + + 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', + }); + }); + + it('Should return status 200 and all items from parentId', async () => { + const { accessToken } = await authService.createTokens(user.id); + + const folder1 = await folderService.createFolder({ + name: 'Folder1', + color: '#123456', + ownerId: otherUser.id, + parentId: null, + }); + await sharingService.createSharing( + { + itemId: folder1.id, + userId: user.id, + }, + otherUser.id, + ); + + const blob = await blobService.createBlob({ + mimeType: 'text/plain', + name: 'test1.txt', + ownerId: otherUser.id, + 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({ + 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({ + 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({ + 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', + url: '/api/item/shared', + headers: { + authorization: 'Bearer ' + accessToken, + }, + }); + + console.log(response.json()); + + expect(response.statusCode).toBe(200); + expect(response.json()).toEqual([ + { + id: expect.any(Number), + name: 'Folder1', + color: '#123456', + parentId: null, + ownerId: otherUser.id, + isStarred: false, + mimeType: 'application/vnd.cloudstore.folder', + createdAt: expect.any(String), + deletedAt: null, + updatedAt: expect.any(String), + }, + { + id: expect.any(Number), + name: 'Folder2', + color: '#987654', + parentId: folder1.id, + ownerId: otherUser.id, + isStarred: false, + mimeType: 'application/vnd.cloudstore.folder', + createdAt: expect.any(String), + deletedAt: null, + updatedAt: expect.any(String), + }, + { + id: expect.any(Number), + name: 'Shortcut', + parentId: folder1.id, + ownerId: otherUser.id, + isStarred: false, + mimeType: 'application/vnd.cloudstore.shortcut', + createdAt: expect.any(String), + deletedAt: null, + updatedAt: expect.any(String), + }, + { + id: expect.any(Number), + name: 'Docs1', + text: 'Docs1 text', + parentId: folder1.id, + ownerId: otherUser.id, + isStarred: false, + mimeType: 'application/vnd.cloudstore.docs', + createdAt: expect.any(String), + deletedAt: null, + updatedAt: expect.any(String), + }, + { + id: expect.any(Number), + name: 'test1.txt', + blobUrl: 'https://example.com/test1.txt', + parentId: folder1.id, + ownerId: otherUser.id, + isStarred: false, + mimeType: 'text/plain', + createdAt: expect.any(String), + deletedAt: null, + updatedAt: expect.any(String), + }, + ]); + }); + + it('Should return status 401, when unauthorized', async () => { + const folder1 = await folderService.createFolder({ + name: 'Folder1', + color: '#123456', + ownerId: otherUser.id, + parentId: null, + }); + await sharingService.createSharing( + { + itemId: folder1.id, + userId: user.id, + }, + otherUser.id, + ); + + const blob = await blobService.createBlob({ + mimeType: 'text/plain', + name: 'test1.txt', + ownerId: otherUser.id, + 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({ + 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({ + 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({ + 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', + url: '/api/item/shared', + headers: { + authorization: 'invalid_token!!', + }, + }); + + expect(response.statusCode).toBe(401); + expect(response.json()).toEqual({ + error: 'UnauthorizedError', + errors: { + _: ['Unauthorized'], + }, + statusCode: 401, + }); + }); +}); diff --git a/src/modules/item/__test__/item.sharings.test.ts b/src/modules/item/__test__/item.sharings.test.ts new file mode 100644 index 0000000..dbe8dd6 --- /dev/null +++ b/src/modules/item/__test__/item.sharings.test.ts @@ -0,0 +1,177 @@ +import { User } from '@prisma/client'; +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'; + +describe('GET /api/item/:id/sharings', () => { + let userService: UserService; + let folderService: FolderService; + let authService: AuthService; + let sharingService: SharingService; + + let user: User; + let otherUser: User; + + beforeAll(async () => { + userService = new UserService(); + folderService = new FolderService(); + authService = new AuthService(); + sharingService = new SharingService(new ItemService()); + + 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', + }); + }); + + it("Should return status 200, item, it's sharings, their users and the owner by itemId", async () => { + const { accessToken } = await authService.createTokens(user.id); + + const folder = await folderService.createFolder({ + name: 'Folder1', + color: '#123456', + ownerId: user.id, + parentId: null, + }); + const sharing = await sharingService.createSharing( + { + itemId: folder.id, + userId: otherUser.id, + }, + user.id, + ); + + const response = await global.fastify.inject({ + method: 'GET', + url: `/api/item/${folder.id}/sharings`, + headers: { + authorization: 'Bearer ' + accessToken, + }, + }); + + expect(response.statusCode).toBe(200); + expect(response.json()).toEqual({ + id: expect.any(Number), + name: 'Folder1', + parentId: null, + ownerId: user.id.toString(), + owner: { + id: user.id, + name: user.name, + email: user.email, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, + ItemSharing: [ + { + id: sharing.id, + userId: sharing.userId, + user: { + id: otherUser.id, + name: otherUser.name, + email: otherUser.email, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, + itemId: sharing.itemId, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, + ], + mimeType: 'application/vnd.cloudstore.folder', + createdAt: expect.any(String), + updatedAt: expect.any(String), + deletedAt: null, + }); + }); + + it("Should return status 200, item, it's sharings, their users and the owner by itemId", async () => { + const { accessToken } = await authService.createTokens(user.id); + + const response = await global.fastify.inject({ + method: 'GET', + url: `/api/item/1234/sharings`, + headers: { + authorization: 'Bearer ' + accessToken, + }, + }); + + expect(response.statusCode).toBe(400); + expect(response.json()).toEqual({ + error: 'BadRequestError', + errors: { + _: ['Item not found'], + }, + statusCode: 400, + }); + }); + + it("Should return status 401, when you don't have access to the item", async () => { + const { accessToken } = await authService.createTokens(otherUser.id); + + const folder = await folderService.createFolder({ + name: 'Folder1', + color: '#123456', + ownerId: user.id, + parentId: null, + }); + + const response = await global.fastify.inject({ + method: 'GET', + url: `/api/item/${folder.id}/sharings`, + headers: { + authorization: 'Bearer ' + accessToken, + }, + }); + + expect(response.statusCode).toBe(401); + expect(response.json()).toEqual({ + error: 'UnauthorizedError', + errors: { + _: ['Unauthorized'], + }, + statusCode: 401, + }); + }); + + it('Should return status 401, when unauthorized', 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 response = await global.fastify.inject({ + method: 'GET', + url: `/api/item/${folder.id}/sharings`, + headers: { + authorization: 'invalid_token!!', + }, + }); + + expect(response.statusCode).toBe(401); + expect(response.json()).toEqual({ + error: 'UnauthorizedError', + errors: { + _: ['Unauthorized'], + }, + statusCode: 401, + }); + }); +}); diff --git a/src/modules/item/item.controller.ts b/src/modules/item/item.controller.ts index 33e847f..5c389ef 100644 --- a/src/modules/item/item.controller.ts +++ b/src/modules/item/item.controller.ts @@ -1,6 +1,6 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import ItemService from './item.service'; -import { ReadInput } from './item.schema'; +import { ReadInput, itemSharingsInput } from './item.schema'; import AccessService from './sharing/access.service'; export default class ItemController { @@ -56,4 +56,41 @@ export default class ItemController { return reply.badRequest(); } } + + public async sharedItemHandler(request: FastifyRequest, reply: FastifyReply) { + try { + const items = await this.itemService.getAllSharedItemsByUserId(request.user.sub); + + return reply.code(200).send(items); + } catch (e) { + /* istanbul ignore next */ + return reply.badRequest(); + } + } + + public async sharingsHandler( + request: FastifyRequest<{ + Params: itemSharingsInput; + }>, + reply: FastifyReply, + ) { + try { + const id = Number.parseInt(request.params.id); + + if (!(await this.accessService.hasAccessToItem(id, request.user.sub))) { + return reply.unauthorized(); + } + + const item = await this.itemService.getItemByIdWithSharingsAndOwner(id); + + return reply.code(200).send(item); + } catch (e) { + if (e instanceof Error) { + return reply.badRequest(request.i18n.t(e.message)); + } + + /* istanbul ignore next */ + return reply.badRequest(); + } + } } diff --git a/src/modules/item/item.route.ts b/src/modules/item/item.route.ts index 2cfbe65..ba124b3 100644 --- a/src/modules/item/item.route.ts +++ b/src/modules/item/item.route.ts @@ -68,4 +68,42 @@ export default async (fastify: FastifyInstance) => { }, itemController.itemHandler.bind(itemController), ); + + fastify.get( + '/shared', + { + schema: { + tags: ['Item'], + response: { + 200: { $ref: 'itemsResponseSchema' }, + }, + security: [ + { + bearerAuth: [], + }, + ], + }, + onRequest: [fastify.authenticate], + }, + itemController.sharedItemHandler.bind(itemController), + ); + + fastify.get( + '/:id/sharings', + { + schema: { + tags: ['Item'], + response: { + 200: { $ref: 'itemSharingsResponseSchema' }, + }, + security: [ + { + bearerAuth: [], + }, + ], + }, + onRequest: [fastify.authenticate], + }, + itemController.sharingsHandler.bind(itemController), + ); }; diff --git a/src/modules/item/item.schema.ts b/src/modules/item/item.schema.ts index b959235..2c16de2 100644 --- a/src/modules/item/item.schema.ts +++ b/src/modules/item/item.schema.ts @@ -91,6 +91,120 @@ const itemsResponseSchema = { }, } as const; +const itemSharingsSchema = { + $id: 'itemSharingsSchema', + type: 'object', + properties: { + id: { + type: 'string', + errorMessage: { + type: 'item.id.type', + }, + }, + }, + required: ['id'], + errorMessage: { + required: { + id: 'item.id.required', + }, + }, +} as const; + +const itemSharingsResponseSchema = { + $id: 'itemSharingsResponseSchema', + type: 'object', + properties: { + id: { + type: 'number', + }, + name: { + type: 'string', + }, + mimeType: { + type: 'string', + }, + ownerId: { + type: 'string', + }, + parentId: { + type: ['number', 'null'], + }, + owner: { + type: 'object', + properties: { + id: { + type: 'number', + }, + email: { + type: 'string', + }, + name: { + type: 'string', + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + }, + }, + ItemSharing: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + userId: { + type: 'number', + }, + user: { + type: 'object', + properties: { + id: { + type: 'number', + }, + email: { + type: 'string', + }, + name: { + type: 'string', + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + }, + }, + itemId: { + type: 'number', + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + }, + }, + }, + deletedAt: { + type: ['string', 'null'], + }, + createdAt: { + type: 'string', + }, + updatedAt: { + type: 'string', + }, + }, +} as const; + +export type itemSharingsInput = FromSchema; export type ReadInput = FromSchema; -export const itemSchemas = [readItemsSchema, itemsResponseSchema]; +export const itemSchemas = [readItemsSchema, itemsResponseSchema, itemSharingsResponseSchema]; diff --git a/src/modules/item/item.service.ts b/src/modules/item/item.service.ts index ebc9a98..719da98 100644 --- a/src/modules/item/item.service.ts +++ b/src/modules/item/item.service.ts @@ -141,6 +141,53 @@ export default class ItemService { return this.formatItems(items); } + public async getAllSharedItemsByUserId(userId: number) { + const items = await prisma.item.findMany({ + where: { + ItemSharing: { + some: { + userId: userId, + }, + }, + }, + include: { + ItemBlob: true, + ItemFolder: true, + ItemDocs: true, + ItemShortcut: true, + ItemStarred: { + where: { + userId: userId, + }, + }, + }, + }); + + return this.formatItems(items); + } + + public async getItemByIdWithSharingsAndOwner(id: number) { + const item = await prisma.item.findUnique({ + where: { + id, + }, + include: { + owner: true, + ItemSharing: { + include: { + user: true, + }, + }, + }, + }); + + if (!item) { + throw new Error('item.notFound'); + } + + return item; + } + private formatItems(items: ItemPrismaProperties[]): ItemWithProperties[] { return items.map((element) => { const { ItemFolder, ItemBlob, ItemDocs, ItemShortcut, ItemStarred, ...strippedElement } = diff --git a/src/modules/item/sharing/__test__/add.test.ts b/src/modules/item/sharing/__test__/add.test.ts index 28bd9bf..646bae3 100644 --- a/src/modules/item/sharing/__test__/add.test.ts +++ b/src/modules/item/sharing/__test__/add.test.ts @@ -52,7 +52,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: item.id, - userId: user.id, + email: user.email, }, }); @@ -112,7 +112,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: folder.id, - userId: otherUser.id, + email: otherUser.email, }, }); @@ -157,7 +157,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: item.id, - userId: user.id, + email: user.email, }, }); @@ -189,7 +189,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: item.id, - userId: user.id, + email: user.email, }, }); @@ -203,6 +203,38 @@ describe('POST /api/sharing', () => { }); }); + it("should return status 400, when user with email doesn't exist", async () => { + const { accessToken } = await authService.createTokens(user.id); + + const item = await itemService.createItem({ + name: 'test.txt', + ownerId: user.id, + parentId: null, + mimeType: 'text/plain', + }); + + const response = await global.fastify.inject({ + method: 'POST', + url: '/api/sharing', + headers: { + authorization: 'Bearer ' + accessToken, + }, + payload: { + itemId: item.id, + email: 'user@whodoesnotexist.com', + }, + }); + + expect(response.statusCode).toBe(400); + expect(response.json()).toEqual({ + error: 'BadRequestError', + errors: { + _: ['User not found'], + }, + statusCode: 400, + }); + }); + it('should return status 400, when sharing already exists', async () => { const { accessToken } = await authService.createTokens(user.id); @@ -228,7 +260,7 @@ describe('POST /api/sharing', () => { authorization: 'Bearer ' + accessToken, }, payload: { - userId: user.id, + email: user.email, itemId: item.id, }, }); @@ -243,7 +275,7 @@ describe('POST /api/sharing', () => { }); }); - it("should return status 400, when user id isn't provided", async () => { + it("should return status 400, when email isn't provided", async () => { const { accessToken } = await authService.createTokens(user.id); const item = await itemService.createItem({ @@ -268,13 +300,13 @@ describe('POST /api/sharing', () => { expect(response.json()).toEqual({ error: 'ValidationError', errors: { - _: ['userId is required'], + _: ['Email is required'], }, statusCode: 400, }); }); - it("should return status 400, when user id isn't a number", async () => { + it("should return status 400, when email isn't a valid email", async () => { const { accessToken } = await authService.createTokens(user.id); const item = await itemService.createItem({ @@ -292,7 +324,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: item.id, - userId: 'invalid_id', + email: 'invalid_email', }, }); @@ -300,7 +332,7 @@ describe('POST /api/sharing', () => { expect(response.json()).toEqual({ error: 'ValidationError', errors: { - userId: ['userId must be a number'], + email: ['Email must be of correct format'], }, statusCode: 400, }); @@ -323,7 +355,7 @@ describe('POST /api/sharing', () => { authorization: 'Bearer ' + accessToken, }, payload: { - userId: user.id, + email: user.email, }, }); @@ -355,7 +387,7 @@ describe('POST /api/sharing', () => { }, payload: { itemId: 'invalid_id', - userId: user.id, + email: user.email, }, }); diff --git a/src/modules/item/sharing/sharing.controller.ts b/src/modules/item/sharing/sharing.controller.ts index 28ee7e4..2f2f531 100644 --- a/src/modules/item/sharing/sharing.controller.ts +++ b/src/modules/item/sharing/sharing.controller.ts @@ -2,14 +2,21 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { AddInput, ReadInput, EditInput, DeleteInput } from './sharing.schema'; import SharingService from './sharing.service'; import AccessService from './access.service'; +import UserService from '../../auth/user.service'; export default class SharingController { private sharingService: SharingService; private accessService: AccessService; + private userService: UserService; - constructor(sharingService: SharingService, accessService: AccessService) { + constructor( + sharingService: SharingService, + accessService: AccessService, + userService: UserService, + ) { this.sharingService = sharingService; this.accessService = accessService; + this.userService = userService; } public async readHandler( @@ -73,7 +80,12 @@ export default class SharingController { return reply.unauthorized(); } - const sharing = await this.sharingService.createSharing(request.body, request.user.sub); + const user = await this.userService.getUserByEmail(request.body.email); + + const sharing = await this.sharingService.createSharing( + { userId: user.id, itemId: request.body.itemId }, + request.user.sub, + ); return reply.code(200).send(sharing); } catch (e) { diff --git a/src/modules/item/sharing/sharing.route.ts b/src/modules/item/sharing/sharing.route.ts index 44492d1..b237e12 100644 --- a/src/modules/item/sharing/sharing.route.ts +++ b/src/modules/item/sharing/sharing.route.ts @@ -3,6 +3,7 @@ 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'; export default async (fastify: FastifyInstance) => { const itemService = new ItemService(); @@ -10,6 +11,7 @@ export default async (fastify: FastifyInstance) => { const sharingController = new SharingController( sharingService, new AccessService(itemService, sharingService), + new UserService(), ); fastify.get( @@ -61,9 +63,9 @@ export default async (fastify: FastifyInstance) => { { schema: { tags: ['Sharing'], - body: { $ref: 'uploadSharingSchema' }, + body: { $ref: 'addSharingSchema' }, response: { - 200: { $ref: 'uploadSharingResponseSchema' }, + 200: { $ref: 'addSharingResponseSchema' }, }, security: [ { diff --git a/src/modules/item/sharing/sharing.schema.ts b/src/modules/item/sharing/sharing.schema.ts index bd7cf95..619e14d 100644 --- a/src/modules/item/sharing/sharing.schema.ts +++ b/src/modules/item/sharing/sharing.schema.ts @@ -13,7 +13,7 @@ export type UpdateSharing = { } & Partial; const addSharingSchema = { - $id: 'uploadSharingSchema', + $id: 'addSharingSchema', type: 'object', properties: { itemId: { @@ -22,25 +22,27 @@ const addSharingSchema = { type: 'item.sharing.itemId.type', }, }, - userId: { - type: 'number', + email: { + type: 'string', + format: 'email', errorMessage: { - type: 'item.sharing.userId.type', + type: 'item.sharing.email.type', + format: 'item.sharing.email.format', }, }, }, - required: ['itemId', 'userId'], + required: ['itemId', 'email'], errorMessage: { required: { itemId: 'item.sharing.itemId.required', - userId: 'item.sharing.userId.required', + email: 'item.sharing.email.required', }, }, } as const; export type AddInput = FromSchema; -const uploadSharingResponseSchema = { - $id: 'uploadSharingResponseSchema', +const addSharingResponseSchema = { + $id: 'addSharingResponseSchema', type: 'object', properties: { id: { @@ -181,7 +183,7 @@ export type DeleteInput = FromSchema; export const sharingSchemas = [ addSharingSchema, - uploadSharingResponseSchema, + addSharingResponseSchema, readSharingSchema, readSharingResponseSchema, editSharingSchema,