Skip to content

Commit

Permalink
Merge pull request #19 from HF6-PROJECT/ara-9
Browse files Browse the repository at this point in the history
feat: Added folder endpoints
  • Loading branch information
Anders164a authored Oct 4, 2023
2 parents 158f5ca + 13f406e commit 5347359
Show file tree
Hide file tree
Showing 15 changed files with 1,323 additions and 0 deletions.
32 changes: 32 additions & 0 deletions prisma/migrations/20231003122910_item_folder/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- DropForeignKey
ALTER TABLE "ItemBlob" DROP CONSTRAINT "ItemBlob_itemId_fkey";

-- DropForeignKey
ALTER TABLE "ItemSharing" DROP CONSTRAINT "ItemSharing_itemId_fkey";

-- DropForeignKey
ALTER TABLE "ItemSharing" DROP CONSTRAINT "ItemSharing_userId_fkey";

-- CreateTable
CREATE TABLE "ItemFolder" (
"id" SERIAL NOT NULL,
"color" VARCHAR(255) NOT NULL,
"itemId" INTEGER NOT NULL,

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

-- CreateIndex
CREATE UNIQUE INDEX "ItemFolder_itemId_key" ON "ItemFolder"("itemId");

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

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

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

-- AddForeignKey
ALTER TABLE "ItemSharing" ADD CONSTRAINT "ItemSharing_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
9 changes: 9 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ model Item {
owner User @relation(fields: [ownerId], references: [id])
parentItem Item? @relation("ItemToItem", fields: [parentId], references: [id])
Items Item[] @relation("ItemToItem")
ItemFolder ItemFolder?
ItemBlob ItemBlob?
ItemSharing ItemSharing[]
Expand All @@ -64,6 +65,14 @@ model Item {
@@index([deletedAt])
}

model ItemFolder {
id Int @id @default(autoincrement())
color String @db.VarChar(255)
itemId Int @unique
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
}

model ItemBlob {
id Int @id @default(autoincrement())
blobUrl String @db.VarChar(1024)
Expand Down
20 changes: 20 additions & 0 deletions src/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@
"notFound": "Delning ikke fundet",
"alreadyExists": "Delning findes allerede"
},
"folder": {
"notFound": "Mappen blev ikke fundet"
},
"notFound": "Item ikke fundet"
},
"folder": {
"id": {
"required": "id er påkrævet",
"type": "id skal være et tal"
},
"name": {
"required": "Navn er påkrævet",
"type": "Navn skal være en tekst"
},
"color": {
"required": "Farve er påkrævet",
"type": "Farve skal være en tekst"
},
"parentId": {
"type": "Parent id skal være et tal"
}
}
}
20 changes: 20 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@
"notFound": "Sharing not found",
"alreadyExists": "Sharing already exists"
},
"folder": {
"notFound": "Folder not found"
},
"notFound": "Item not found"
},
"folder": {
"id": {
"required": "id is required",
"type": "id must be a number"
},
"name": {
"required": "Name is required",
"type": "Name must be a string"
},
"color": {
"required": "Color is required",
"type": "Color must be a string"
},
"parentId": {
"type": "Parent id must be a number"
}
}
}
6 changes: 6 additions & 0 deletions src/modules/item/blob/__test__/delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { User } from '@prisma/client';
import UserService from '../../../auth/user.service';
import AuthService from '../../../auth/auth.service';
import BlobService from '../blob.service';
import ItemService from '../../item.service';

describe('DELETE /api/blob/:id', () => {
let userService: UserService;
let authService: AuthService;
let blobService: BlobService;
let itemService: ItemService;

let user: User;
let otherUser: User;
Expand All @@ -15,6 +17,7 @@ describe('DELETE /api/blob/:id', () => {
authService = new AuthService();
userService = new UserService();
blobService = new BlobService();
itemService = new ItemService();

user = await userService.createUser({
name: 'Joe Biden the 1st',
Expand Down Expand Up @@ -49,6 +52,9 @@ describe('DELETE /api/blob/:id', () => {

expect(response.statusCode).toBe(204);
expect(response.body).toEqual('');

await expect(blobService.getByItemId(blob.id)).rejects.toThrowError();
await expect(itemService.getById(blob.id)).rejects.toThrowError();
});

it('should return status 401, when unauthorized', async () => {
Expand Down
192 changes: 192 additions & 0 deletions src/modules/item/folder/__test__/add.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { User } from '@prisma/client';
import UserService from '../../../auth/user.service';
import AuthService from '../../../auth/auth.service';
import FolderService from '../folder.service';

describe('POST /api/folder', () => {
let userService: UserService;
let authService: AuthService;
let folderService: FolderService;

let user: User;
let otherUser: User;

beforeAll(async () => {
authService = new AuthService();
userService = new UserService();
folderService = new FolderService();

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 200 and folder', async () => {
const { accessToken } = await authService.createTokens(user.id);

const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'Bearer ' + accessToken,
},
payload: {
name: 'Folder Name',
color: '#78BC61',
},
});

expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({
id: expect.any(Number),
name: 'Folder Name',
color: '#78BC61',
parentId: null,
ownerId: user.id,
mimeType: 'application/vnd.cloudstore.folder',
createdAt: expect.any(String),
deletedAt: null,
updatedAt: expect.any(String),
});
});

it('should return status 401, when unauthorized', async () => {
const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'invalid_access_token!!!',
},
payload: {
name: 'Folder Name',
color: '#78BC61',
parentId: null,
},
});

expect(response.statusCode).toBe(401);
expect(response.json()).toEqual({
error: 'UnauthorizedError',
errors: {
_: ['Unauthorized'],
},
statusCode: 401,
});
});

it('should return status 401, when parent id is provided, but no access to parent', async () => {
const { accessToken } = await authService.createTokens(user.id);

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

const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'Bearer ' + accessToken,
},
payload: {
name: 'Folder Name',
color: '#78BC61',
parentId: folder.id,
},
});

expect(response.statusCode).toBe(401);
expect(response.json()).toEqual({
error: 'UnauthorizedError',
errors: {
_: ['Unauthorized'],
},
statusCode: 401,
});
});

it('should return status 401, when folder name is not provided', async () => {
const { accessToken } = await authService.createTokens(user.id);

const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'Bearer ' + accessToken,
},
payload: {
color: '#78BC61',
parentId: null,
},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toEqual({
error: 'ValidationError',
errors: {
_: ['Name is required'],
},
statusCode: 400,
});
});

it('should return status 401, when folder color is not provided', async () => {
const { accessToken } = await authService.createTokens(user.id);

const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'Bearer ' + accessToken,
},
payload: {
name: 'Folder name',
parentId: null,
},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toEqual({
error: 'ValidationError',
errors: {
_: ['Color is required'],
},
statusCode: 400,
});
});

it("should return status 400, when parent id isn't a number", async () => {
const { accessToken } = await authService.createTokens(user.id);

const response = await global.fastify.inject({
method: 'POST',
url: '/api/folder',
headers: {
authorization: 'Bearer ' + accessToken,
},
payload: {
name: 'Folder Name',
color: '#78BC61',
parentId: 'invalid_id',
},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toEqual({
error: 'ValidationError',
errors: {
parentId: ['Parent id must be a number'],
},
statusCode: 400,
});
});
});
Loading

0 comments on commit 5347359

Please sign in to comment.