diff --git a/prisma/migrations/20230202114228_constraints_snippet_and_snippet_tag/migration.sql b/prisma/migrations/20230202114228_constraints_snippet_and_snippet_tag/migration.sql new file mode 100644 index 0000000..5b81773 --- /dev/null +++ b/prisma/migrations/20230202114228_constraints_snippet_and_snippet_tag/migration.sql @@ -0,0 +1,17 @@ +-- DropForeignKey +ALTER TABLE `Snippet` DROP FOREIGN KEY `Snippet_category_id_fkey`; + +-- DropForeignKey +ALTER TABLE `Snippet_tag` DROP FOREIGN KEY `Snippet_tag_snippet_id_fkey`; + +-- DropForeignKey +ALTER TABLE `Snippet_tag` DROP FOREIGN KEY `Snippet_tag_tag_id_fkey`; + +-- AddForeignKey +ALTER TABLE `Snippet` ADD CONSTRAINT `Snippet_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `Category`(`id`) ON DELETE SET NULL ON UPDATE SET NULL; + +-- AddForeignKey +ALTER TABLE `Snippet_tag` ADD CONSTRAINT `Snippet_tag_tag_id_fkey` FOREIGN KEY (`tag_id`) REFERENCES `Tag`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Snippet_tag` ADD CONSTRAINT `Snippet_tag_snippet_id_fkey` FOREIGN KEY (`snippet_id`) REFERENCES `Snippet`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d3552ed..e3ce1d4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -30,7 +30,7 @@ model Snippet { user_id Int user User @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade) category_id Int? - category Category? @relation(fields: [category_id], references: [id]) + category Category? @relation(fields: [category_id], references: [id], onDelete: SetNull, onUpdate: SetNull) language_id Int language Language @relation(fields: [language_id], references: [id]) Snippet_tag Snippet_tag[] @@ -52,9 +52,9 @@ model Language { model Snippet_tag { tag_id Int - tag Tag @relation(fields: [tag_id], references: [id]) + tag Tag @relation(fields: [tag_id], references: [id], onDelete: Cascade, onUpdate: Cascade) snippet_id Int - snippet Snippet @relation(fields: [snippet_id], references: [id]) + snippet Snippet @relation(fields: [snippet_id], references: [id], onDelete: Cascade, onUpdate: Cascade) @@id([tag_id, snippet_id]) } diff --git a/src/__tests__/app.test.ts b/src/__tests__/app.test.ts index c685233..03f9278 100644 --- a/src/__tests__/app.test.ts +++ b/src/__tests__/app.test.ts @@ -181,18 +181,6 @@ describe('PUT /v1/category/:id', () => { }) }) -describe('DELETE /v1/category/:id', () => { - it('returns status code 200 and a success message', async () => { - const res = await request(app) - .delete(`/v1/category/${category_id}`) - .set('Content-Type', 'application/json') - .set('Cookie', jwtCookie as string) - - expect(res.status).toEqual(200) - expect(res.body).toHaveProperty('message') - }) -}) - describe('POST /v1/snippet', () => { it('returns status code 200 and the snippets', async () => { const res = await request(app) @@ -209,7 +197,9 @@ describe('POST /v1/snippet', () => { `, - language: 'vue' + language: 'vue', + tags: ['template', 'vuejs'], + category_id })) expect(res.status).toEqual(200) @@ -228,18 +218,7 @@ describe('GET /v1/snippet/', () => { expect(res.status).toEqual(200) expect(res.body.snippets[0].title).toEqual('Vue3 CompAPI TS script-template-style') - }) -}) - -describe('GET /v1/snippet/:id', () => { - it('returns status code 200 and one snippet', async () => { - const res = await request(app) - .get(`/v1/snippet/${snippet_id}`) - .set('Content-Type', 'application/json') - .set('Cookie', jwtCookie as string) - - expect(res.status).toEqual(200) - expect(res.body.snippet.title).toEqual('Vue3 CompAPI TS script-template-style') + expect(res.body.snippets[0].category_id).toEqual(category_id) }) }) @@ -251,7 +230,8 @@ describe('PUT /v1/snippet/:id', () => { .set('Cookie', jwtCookie as string) .send(JSON.stringify({ code: '
en fait non à vuejs
', - // language: 'html' + language: 'html', + tags: ['pouet', 'pouet', 'pouet'] })) expect(res.status).toEqual(200) @@ -259,6 +239,21 @@ describe('PUT /v1/snippet/:id', () => { }) }) + +describe('GET /v1/snippet/:id', () => { + it('returns status code 200 and one snippet', async () => { + const res = await request(app) + .get(`/v1/snippet/${snippet_id}`) + .set('Content-Type', 'application/json') + .set('Cookie', jwtCookie as string) + + expect(res.status).toEqual(200) + expect(res.body.snippet.tags.length).toEqual(3) + expect(res.body.snippet.tags).not.toContain('vuejs') + expect(res.body.snippet.category_id).toEqual(category_id) + }) +}) + describe('DELETE /v1/snippet/:id', () => { it('returns status code 200 and success message', async () => { const res = await request(app) @@ -271,6 +266,18 @@ describe('DELETE /v1/snippet/:id', () => { }) }) +describe('DELETE /v1/category/:id', () => { + it('returns status code 200 and a success message', async () => { + const res = await request(app) + .delete(`/v1/category/${category_id}`) + .set('Content-Type', 'application/json') + .set('Cookie', jwtCookie as string) + + expect(res.status).toEqual(200) + expect(res.body).toHaveProperty('message') + }) +}) + describe('PUT /v1/user', () => { it('returns status code 200 and success message', async () => { const res = await request(app) diff --git a/src/app.ts b/src/app.ts index c9db9ba..95b21c2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,13 +1,14 @@ import express, { RequestHandler } from 'express' -import userRouter from './routers/user.js' import * as dotenv from 'dotenv' import jwt from 'jsonwebtoken' import cors from 'cors' import cookieParser from 'cookie-parser' import { getJwtSecret } from './utils/jwt.js' + +import userRouter from './routers/user.js' import sessionRouter from './routers/session.js' import categoryRouter from './routers/category.js' -import snippetRouter from './routers/snippet' +import snippetRouter from './routers/snippet.js' const authMiddleware: RequestHandler = (req, res, next) => { const token = req.cookies.jwt diff --git a/src/routers/snippet.ts b/src/routers/snippet.ts index b4f4549..4a18e87 100644 --- a/src/routers/snippet.ts +++ b/src/routers/snippet.ts @@ -9,19 +9,29 @@ const prisma = new PrismaClient() const snippetPostParser = z.object({ code: z.string(), title: z.string().max(50), - language: z.string().max(32) + language: z.string().max(32), + tags: z.array(z.string()), + category_id: z.number() }).required() const snippetUpdateParser = z.object({ code: z.string().optional(), title: z.string().max(50).optional(), - language: z.string().max(32).optional() + language: z.string().max(32).optional(), + tags: z.array(z.string()).optional() }) const paramsIdParser = z.object({ id: z.coerce.number(), }).required() +function shortenSnippetsTagDepth(snippets: Snippet[]) { + snippets.forEach((snippet: any) => { + snippet.tags = snippet.Snippet_tag.map((x: any) => ({ ...x.tag })) + snippet.Snippet_tag = undefined + }) +} + // TODO Ajouter paramètres sur la requête pour rechercher des snippets // TODO Ajouter pagination const snippetGet: RequestHandler = async (req, res) => { @@ -29,8 +39,14 @@ const snippetGet: RequestHandler = async (req, res) => { try { snippets = await prisma.snippet.findMany({ - where: { user_id: req.body.userId } + where: { user_id: req.body.userId }, + include: { + Snippet_tag: { + select: { tag: { select: { id: true, name: true } } }, + } + } }) + shortenSnippetsTagDepth(snippets) } catch (error: any) { res.status(400).json({ message: (error.issues ?? error) }) return; @@ -47,8 +63,16 @@ const snippetGetUnique: RequestHandler = async (req, res) => { where: { id: paramsIdParser.parse(req.params).id, user_id: req.body.userId + }, + include: { + Snippet_tag: { + select: { tag: { select: { id: true, name: true } } }, + } } }) + if (snippet != undefined) { + shortenSnippetsTagDepth([snippet]) + } } catch (error: any) { res.status(400).json({ message: (error.issues ?? error) }) return; @@ -57,10 +81,60 @@ const snippetGetUnique: RequestHandler = async (req, res) => { res.json({ snippet }) } +async function deleteSnippetTags(userId: number, snippetId: number): Promise