Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added starred endpoints #26

Merged
merged 5 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions prisma/migrations/20231010082448_item_starred/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- CreateTable
CREATE TABLE "ItemStarred" (
"id" SERIAL NOT NULL,
"itemId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,

CONSTRAINT "ItemStarred_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "ItemStarred_itemId_idx" ON "ItemStarred"("itemId");

-- CreateIndex
CREATE INDEX "ItemStarred_userId_idx" ON "ItemStarred"("userId");

-- CreateIndex
CREATE UNIQUE INDEX "ItemStarred_itemId_userId_key" ON "ItemStarred"("itemId", "userId");

-- AddForeignKey
ALTER TABLE "ItemStarred" ADD CONSTRAINT "ItemStarred_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "ItemStarred" ADD CONSTRAINT "ItemStarred_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
15 changes: 15 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ model User {
sessions UserSession[]
Item Item[]
ItemSharing ItemSharing[]
ItemStarred ItemStarred[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Expand Down Expand Up @@ -57,6 +58,7 @@ model Item {
ItemSharing ItemSharing[]
ItemShortcut ItemShortcut? @relation("shortcutItem")
LinkedItemShortcut ItemShortcut[] @relation("linkedItem")
ItemStarred ItemStarred[]

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Expand Down Expand Up @@ -118,3 +120,16 @@ model ItemShortcut {

@@index([linkedItemId])
}

model ItemStarred {
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)

Anders164a marked this conversation as resolved.
Show resolved Hide resolved
@@unique([itemId, userId])
@@index([itemId])
@@index([userId])
}
14 changes: 14 additions & 0 deletions src/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"shortcut": {
"notFound": "Genvej blev ikke fundet"
},
"starred": {
"notFound": "Stjernemarkeringen blev ikke fundet",
"alreadyExists": "Stjernemarkeringen findes allerede"
},
"notFound": "Item ikke fundet"
},
"folder": {
Expand Down Expand Up @@ -163,5 +167,15 @@
"parentId": {
"type": "Parent id skal være et tal"
}
},
"starred": {
"id": {
"required": "id er påkrævet",
"type": "id skal være et tal"
},
"itemId": {
"required": "itemId er påkrævet",
"type": "itemId skal være et tal"
}
}
}
14 changes: 14 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"shortcut": {
"notFound": "Shortcut not found"
},
"starred": {
"notFound": "Starred not found",
"alreadyExists": "Starred already exists"
},
"notFound": "Item not found"
},
"folder": {
Expand Down Expand Up @@ -163,5 +167,15 @@
"parentId": {
"type": "Parent id must be a number"
}
},
"starred": {
"id": {
"required": "id is required",
"type": "id must be a number"
},
"itemId": {
"required": "itemId is required",
"type": "itemId must be a number"
}
}
}
204 changes: 204 additions & 0 deletions src/modules/item/__test__/item.starred.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { User } from '@prisma/client';
import UserService from '../../auth/user.service';
import AuthService from '../../auth/auth.service';
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';

describe('GET /api/item/starred', () => {
let userService: UserService;
let authService: AuthService;
let starredService: StarredService;
let folderService: FolderService;
let blobService: BlobService;
let sharingService: SharingService;

let user: User;
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());

user = await userService.createUser({
name: 'Joe Biden the 1st',
email: '[email protected]',
password: '1234',
});
otherUser = await userService.createUser({
name: 'Joe Biden the 2nd',
email: '[email protected]',
password: '4321',
});
});

it('should return status 400, when item starred browse is empty', async () => {
const { accessToken } = await authService.createTokens(user.id);

const response = await global.fastify.inject({
method: 'GET',
url: '/api/item/starred',
headers: {
authorization: 'Bearer ' + accessToken,
},
});

expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual([]);
});

it('should return status 200 and items', async () => {
const { accessToken } = await authService.createTokens(user.id);

const folder1 = await folderService.createFolder({
name: 'Folder1',
ownerId: user.id,
parentId: null,
color: '#78BC61',
});

const folder2 = await folderService.createFolder({
name: 'Folder2',
ownerId: user.id,
parentId: null,
color: '#79BC61',
});

const blob = await blobService.createBlob({
mimeType: 'text/plain',
name: 'test1.txt',
ownerId: user.id,
parentId: null,
blobUrl: 'https://example.com/test1.txt',
});

const blob2 = await blobService.createBlob({
mimeType: 'text/plain',
name: 'test2.txt',
ownerId: otherUser.id,
parentId: null,
blobUrl: 'https://example.com/test2.txt',
});

await starredService.createStarred({
itemId: folder1.id,
userId: user.id,
});

await starredService.createStarred({
itemId: folder2.id,
userId: user.id,
});

await starredService.createStarred({
itemId: blob.id,
userId: user.id,
});

await sharingService.createSharing(
{
itemId: blob2.id,
userId: user.id,
},
otherUser.id,
);

await starredService.createStarred({
itemId: blob2.id,
userId: user.id,
});

const response = await global.fastify.inject({
method: 'GET',
url: '/api/item/starred',
headers: {
authorization: 'Bearer ' + accessToken,
},
});

expect(response.statusCode).toBe(200);
expect(response.json()).toEqual([
{
id: folder1.id,
name: 'Folder1',
color: '#78BC61',
parentId: null,
ownerId: user.id,
mimeType: 'application/vnd.cloudstore.folder',
createdAt: expect.any(String),
deletedAt: null,
updatedAt: expect.any(String),
},
{
id: blob2.id,
name: 'test2.txt',
blobUrl: 'https://example.com/test2.txt',
parentId: null,
ownerId: otherUser.id,
mimeType: 'text/plain',
createdAt: expect.any(String),
deletedAt: null,
updatedAt: expect.any(String),
},
{
id: folder2.id,
name: 'Folder2',
color: '#79BC61',
parentId: null,
ownerId: user.id,
mimeType: 'application/vnd.cloudstore.folder',
createdAt: expect.any(String),
deletedAt: null,
updatedAt: expect.any(String),
},
{
id: blob.id,
name: 'test1.txt',
blobUrl: 'https://example.com/test1.txt',
parentId: null,
ownerId: user.id,
mimeType: 'text/plain',
createdAt: expect.any(String),
deletedAt: null,
updatedAt: expect.any(String),
},
]);
});

it('should return status 401, when unauthorized', async () => {
const folder = await folderService.createFolder({
name: 'Folder1',
ownerId: user.id,
parentId: null,
color: '#78BC61',
});

await starredService.createStarred({
itemId: folder.id,
userId: user.id,
});

const response = await global.fastify.inject({
method: 'GET',
url: '/api/item/starred',
headers: {
authorization: 'invalid_access_token!!!',
},
});

expect(response.statusCode).toBe(401);
expect(response.json()).toEqual({
error: 'UnauthorizedError',
errors: {
_: ['Unauthorized'],
},
statusCode: 401,
});
});
});
2 changes: 2 additions & 0 deletions src/modules/item/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import folder from './folder';
import sharing from './sharing';
import itemRoute from './item.route';
import shortcut from './shortcut';
import starred from './starred';
import { itemSchemas } from './item.schema';

export default fastifyPlugin(async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
Expand All @@ -15,6 +16,7 @@ export default fastifyPlugin(async (fastify: FastifyInstance, options: FastifyPl
await fastify.register(folder, getOptionsWithPrefix(options, '/folder'));
await fastify.register(sharing, getOptionsWithPrefix(options, '/sharing'));
await fastify.register(shortcut, getOptionsWithPrefix(options, '/shortcut'));
await fastify.register(starred, getOptionsWithPrefix(options, '/starred'));
await fastify.register(itemRoute, getOptionsWithPrefix(options, '/item'));

for (const schema of itemSchemas) {
Expand Down
11 changes: 11 additions & 0 deletions src/modules/item/item.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ export default class ItemController {
this.accessService = accessService;
}

public async itemStarredHandler(request: FastifyRequest, reply: FastifyReply) {
try {
const starred = await this.itemService.getStarredItemsByUserId(request.user.sub);

return reply.code(200).send(starred);
} catch (e) {
/* istanbul ignore next */
return reply.badRequest();
}
}

public async itemRootHandler(request: FastifyRequest, reply: FastifyReply) {
try {
const items = await this.itemService.getByOwnerIdAndParentId(request.user.sub, null);
Expand Down
19 changes: 19 additions & 0 deletions src/modules/item/item.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ export default async (fastify: FastifyInstance) => {
new AccessService(itemService, new SharingService(itemService)),
);

fastify.get(
'/starred',
{
schema: {
tags: ['Item'],
response: {
200: { $ref: 'itemsResponseSchema' },
},
security: [
{
bearerAuth: [],
},
],
},
onRequest: [fastify.authenticate],
},
itemController.itemStarredHandler.bind(itemController),
);

fastify.get(
'/',
{
Expand Down
Loading