From d8e10fd861474da5e9749ad806db6bd70651f1e2 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 23 Nov 2023 20:57:28 +0100 Subject: [PATCH] feat(delete-feed-route): delete feed by id route done --- back/__tests__/controllers/feeds.test.ts | 59 ++++++++++++++++++++ back/insomnia.json | 2 +- back/src/controllers/articles.ts | 4 +- back/src/controllers/feeds.ts | 10 +++- back/src/database/feeds.ts | 8 +++ back/src/middlewares.ts | 8 ++- back/src/types/dto/articles/OneArticleDTO.ts | 7 --- back/src/types/dto/urlParamIdDTO.ts | 7 +++ 8 files changed, 92 insertions(+), 13 deletions(-) delete mode 100644 back/src/types/dto/articles/OneArticleDTO.ts create mode 100644 back/src/types/dto/urlParamIdDTO.ts diff --git a/back/__tests__/controllers/feeds.test.ts b/back/__tests__/controllers/feeds.test.ts index 983ea77..eb45cc6 100644 --- a/back/__tests__/controllers/feeds.test.ts +++ b/back/__tests__/controllers/feeds.test.ts @@ -53,6 +53,65 @@ describe('Feeds controller tests', () => { await deleteAllUsers(database); }); }); + + describe('DELETE feed by id', () => { + beforeAll(async () => { + await populateFeeds(database); + const res = await request(app).post('/api/users/register').send({ + email: 'alexis.moins@epitech.eu', + password: 'mySecretPassword', + username: 'Alexis', + }); + authToken = res.body.token; + registeredUserId = res.body.user.id; + }); + + test('DELETE feed unauthenticated', async () => { + const res = await request(app).delete('/api/feeds/1'); + expect(res.statusCode).toStrictEqual(HttpStatusCode.UNAUTHORIZED_401); + }); + + test('DELETE feed authenticated as non admin', async () => { + const res = await request(app) + .delete('/api/feeds/1') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.FORBIDDEN_403); + }); + + test('DELETE feed authenticated as admin', async () => { + await setRegisteredUserAdmin(registeredUserId); + const res = await request(app) + .delete('/api/feeds/1') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.OK_200); + + const deleteFeed = await database.feed.findUnique({ + where: { + id: 1, + }, + }); + expect(deleteFeed).toBeNull(); + }); + + test('DELETE feed with non existing id', async () => { + const res = await request(app) + .delete('/api/feeds/0') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.NOT_FOUND_404); + }); + + test('DELETE feed with invalid id format', async () => { + const res = await request(app) + .delete('/api/feeds/nzendiezn') + .set('Authorization', `Bearer ${authToken}`); + expect(res.statusCode).toStrictEqual(HttpStatusCode.BAD_REQUEST_400); + }); + + afterAll(async () => { + await deleteAllFeeds(database); + await deleteAllUsers(database); + }); + }); }); async function setRegisteredUserAdmin(userId: string) { diff --git a/back/insomnia.json b/back/insomnia.json index 2d6dd4a..c779832 100644 --- a/back/insomnia.json +++ b/back/insomnia.json @@ -1 +1 @@ -{"_type":"export","__export_format":4,"__export_date":"2023-11-21T16:11:15.725Z","__export_source":"insomnia.desktop.app:v8.4.2","resources":[{"_id":"req_48edfa91da4e4baf89e1c451940ca502","parentId":"fld_7dfe6474a7fe4c66a734b163ac9511cd","modified":1700574327677,"created":1699275753161,"url":"{{ _.url }}/users/login","name":"LOGIN","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"email\": \"alexis.moins@epitech.eu\",\n\t\"password\": \"mySecretPassword\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1699277626516,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_7dfe6474a7fe4c66a734b163ac9511cd","parentId":"fld_8572aff577dd4975b461bd785072416a","modified":1700558051699,"created":1699277617711,"name":"AUTH","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700557585613,"_type":"request_group"},{"_id":"fld_8572aff577dd4975b461bd785072416a","parentId":"wrk_scratchpad","modified":1700557577457,"created":1700557577457,"name":"USERS","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700557577457,"_type":"request_group"},{"_id":"wrk_scratchpad","parentId":null,"modified":1698054281901,"created":1698054281901,"name":"Scratch Pad","description":"","scope":"collection","_type":"workspace"},{"_id":"req_c9b6ded157db4820bbc5fe48279936bd","parentId":"fld_7dfe6474a7fe4c66a734b163ac9511cd","modified":1700577607674,"created":1698055032798,"url":"{{ _.url }}/users/register","name":"REGISTER","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"email\": \"alexis.moins@epitech.eu\",\n\t\"password\": \"mySecretPassword\",\n\t\"username\": \"Alexis\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1699277626416,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_5d25b68ba33345878946e5296f24b304","parentId":"fld_7dfe6474a7fe4c66a734b163ac9511cd","modified":1700576649972,"created":1700558031049,"url":"{{ _.url }}/users/logout","name":"LOGOUT 🔒","description":"","method":"POST","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699277626316,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b21c2920ab4d48b3af187ecbc150318b","parentId":"fld_8572aff577dd4975b461bd785072416a","modified":1700576659458,"created":1700576596454,"url":"{{ _.url }}/users/profile","name":"PROFILE 🔒","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699917606014.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0aa5e8de7f3d41fe8cfb8be29424f2ef","parentId":"fld_8572aff577dd4975b461bd785072416a","modified":1700581264921,"created":1700580307718,"url":"{{ _.url }}/users/profile","name":"UPDATE 🔒","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n\t\"username\": \"Alexis\",\n\t\"currency\": \"USD\",\n\t\"keywords\": [\n\t\t\"ethereum\"\n\t],\n\t\"cryptos\": [\n\t\t1\n\t]\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699597616265.25,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_99d30891da4bdcebc63947a8fc17f076de878684","parentId":"wrk_scratchpad","modified":1700580828379,"created":1698054913210,"name":"Base Environment","data":{"url":"localhost:4000/api","token":"mj7vunia97bxnr81c7pugt5ih4bamarge1k8exxn","userID":1},"dataPropertyOrder":{"&":["url","token","userID"]},"color":null,"isPrivate":false,"metaSortKey":1698054913210,"_type":"environment"},{"_id":"jar_99d30891da4bdcebc63947a8fc17f076de878684","parentId":"wrk_scratchpad","modified":1700557924945,"created":1698054913215,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file +{"_type":"export","__export_format":4,"__export_date":"2023-11-21T22:34:37.995Z","__export_source":"insomnia.desktop.app:v8.4.2","resources":[{"_id":"req_accb7141a1c94119ac9bbe45524b417f","parentId":"fld_df03e117423f4903b236585bc67cc9d6","modified":1700605879774,"created":1700605512023,"url":"{{ _.url }}/feeds/1","name":"DELETE BY ID","description":"","method":"DELETE","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"02gvtn30flu8ousdk80ri4x31wtaa3m3drtm9day","prefix":"Bearer"},"metaSortKey":-1700605512023,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_df03e117423f4903b236585bc67cc9d6","parentId":"wrk_47a49ef1618642b2ba8bf8089eb51a49","modified":1700601079368,"created":1700601079368,"name":"FEEDS","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700601079368,"_type":"request_group"},{"_id":"wrk_47a49ef1618642b2ba8bf8089eb51a49","parentId":null,"modified":1700593836329,"created":1700593821162,"name":"Count of Money API","description":"","scope":"collection","_type":"workspace"},{"_id":"req_d3b0d540afdb4f1d8eb26808caedd79c","parentId":"fld_df03e117423f4903b236585bc67cc9d6","modified":1700601236865,"created":1700601107315,"url":"{{ _.url }}/feeds","name":"ALL FEEDS 🅰️","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"ieg1zz4x75yln8jtn309ucmmn1hs6zgg3cjf117h","prefix":"Bearer"},"metaSortKey":-1700601113654,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_40f5f08d0ebc423ba0205a4dc0a2b3e3","parentId":"fld_0adeceae039d47caac5cb8d59d535a13","modified":1700595998689,"created":1700594400752,"url":"{{ _.url }}/articles","name":"Articles as anonymous","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700594400752,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_0adeceae039d47caac5cb8d59d535a13","parentId":"wrk_47a49ef1618642b2ba8bf8089eb51a49","modified":1700601099909,"created":1700593848098,"name":"ARTICLES","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700593848099,"_type":"request_group"},{"_id":"req_1561c2c8679c48369e9630b2d7aa6378","parentId":"fld_0adeceae039d47caac5cb8d59d535a13","modified":1700602481814,"created":1700593972741,"url":"{{ _.url }}/articles?keywords=bitcoin","name":"Articles by keywords 👤","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"av1f7s0y6qx8f3x8o4yw1rjz8pzo2celx2brhvmt","prefix":"Bearer"},"metaSortKey":-1700593972741,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_278c073318b1472583201e8c3f795495","parentId":"fld_0adeceae039d47caac5cb8d59d535a13","modified":1700596011147,"created":1700593853133,"url":"{{ _.url }}/articles/1","name":"One article by id","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700593853133,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_7b5d4097d5304384a8be47a7e9e7105d","parentId":"fld_afa36486f6aa465a821ca115c050b467","modified":1700574327677,"created":1699275753161,"url":"{{ _.url }}/users/login","name":"LOGIN","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"email\": \"alexis.moins@epitech.eu\",\n\t\"password\": \"mySecretPassword\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1699277626516,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_afa36486f6aa465a821ca115c050b467","parentId":"fld_45c4f1bcbaa24df6a5948a8c30342512","modified":1700558051699,"created":1699277617711,"name":"AUTH","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700557585613,"_type":"request_group"},{"_id":"fld_45c4f1bcbaa24df6a5948a8c30342512","parentId":"wrk_47a49ef1618642b2ba8bf8089eb51a49","modified":1700557577457,"created":1700557577457,"name":"USERS","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700557577457,"_type":"request_group"},{"_id":"req_fb7934c4e0d743dbadead4312bc04ba3","parentId":"fld_afa36486f6aa465a821ca115c050b467","modified":1700605597566,"created":1698055032798,"url":"{{ _.url }}/users/register","name":"REGISTER","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"email\": \"alexis.moins@epitech.eu\",\n\t\"password\": \"mySecretPassword\",\n\t\"username\": \"Alexis\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1699277626416,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_bc92fa6e9104494d92b4fa5a934a5608","parentId":"fld_afa36486f6aa465a821ca115c050b467","modified":1700601142192,"created":1700558031049,"url":"{{ _.url }}/users/logout","name":"LOGOUT 🔒","description":"","method":"POST","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699277626316,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_698eec6ade06455fafbbb38c09da1024","parentId":"fld_45c4f1bcbaa24df6a5948a8c30342512","modified":1700576659458,"created":1700576596454,"url":"{{ _.url }}/users/profile","name":"PROFILE 🔒","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699917606014.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_eb2611ad3eb64e569fa5f16f40d7949c","parentId":"fld_45c4f1bcbaa24df6a5948a8c30342512","modified":1700581264921,"created":1700580307718,"url":"{{ _.url }}/users/profile","name":"UPDATE 🔒","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n\t\"username\": \"Alexis\",\n\t\"currency\": \"USD\",\n\t\"keywords\": [\n\t\t\"ethereum\"\n\t],\n\t\"cryptos\": [\n\t\t1\n\t]\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{"type":"bearer","token":"{{ _.token }}","prefix":"Bearer"},"metaSortKey":-1699597616265.25,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_4501f8c1e77f499690a5b9d7fef2e5bb","parentId":"wrk_47a49ef1618642b2ba8bf8089eb51a49","modified":1700580828379,"created":1698054913210,"name":"Base Environment","data":{"url":"localhost:4000/api","token":"mj7vunia97bxnr81c7pugt5ih4bamarge1k8exxn","userID":1},"dataPropertyOrder":{"&":["url","token","userID"]},"color":null,"isPrivate":false,"metaSortKey":1698054913210,"_type":"environment"},{"_id":"jar_3898e942b6a24e5488e0060431cf476d","parentId":"wrk_47a49ef1618642b2ba8bf8089eb51a49","modified":1700557924945,"created":1698054913215,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file diff --git a/back/src/controllers/articles.ts b/back/src/controllers/articles.ts index e678faf..c3a3514 100644 --- a/back/src/controllers/articles.ts +++ b/back/src/controllers/articles.ts @@ -1,6 +1,6 @@ import HttpStatusCode from '#types/HttpStatusCode'; import AllArticlesDTO from '#types/dto/articles/AllArticlesDTO'; -import OneArticleDTO from '#types/dto/articles/OneArticleDTO'; +import urlParamIdDTO from '#types/dto/urlParamIdDTO'; import { Article } from '@prisma/client'; import express from 'express'; import ApiErrors, { APIError } from '~apiErrors'; @@ -29,7 +29,7 @@ controller.get('/', isAuthenticated, async (req, res) => { }); controller.get('/:id', async (req, res) => { - const urlParams = OneArticleDTO.parse(req.params); + const urlParams = urlParamIdDTO.parse(req.params); const article = await findArticleById(urlParams.id); if (article === null) throw new APIError(ApiErrors.RESOURCE_NOT_FOUND, 404); return res.status(HttpStatusCode.OK_200).send(article); diff --git a/back/src/controllers/feeds.ts b/back/src/controllers/feeds.ts index 9f9373c..0809bfa 100644 --- a/back/src/controllers/feeds.ts +++ b/back/src/controllers/feeds.ts @@ -1,6 +1,7 @@ import express from 'express'; import HttpStatusCode from '#types/HttpStatusCode'; -import { findAllFeeds } from '../database/feeds'; +import { deleteFeedById, findAllFeeds } from '../database/feeds'; +import urlParamIdDTO from '#types/dto/urlParamIdDTO'; const controller = express.Router(); @@ -8,4 +9,11 @@ controller.get('/', async (req, res) => { return res.status(HttpStatusCode.OK_200).send(await findAllFeeds()); }); +controller.delete('/:id', async (req, res) => { + const urlParams = urlParamIdDTO.parse(req.params); + return res + .status(HttpStatusCode.OK_200) + .send(await deleteFeedById(urlParams.id)); +}); + export default controller; diff --git a/back/src/database/feeds.ts b/back/src/database/feeds.ts index 74f746e..cd7faeb 100644 --- a/back/src/database/feeds.ts +++ b/back/src/database/feeds.ts @@ -4,3 +4,11 @@ import { database } from '~lucia'; export async function findAllFeeds(): Promise { return await database.feed.findMany(); } + +export async function deleteFeedById(feedId: number): Promise { + return await database.feed.delete({ + where: { + id: feedId, + }, + }); +} diff --git a/back/src/middlewares.ts b/back/src/middlewares.ts index f1353a6..d37c2df 100644 --- a/back/src/middlewares.ts +++ b/back/src/middlewares.ts @@ -1,8 +1,8 @@ import { NextFunction, Request, Response } from 'express'; import { LuciaError } from 'lucia'; -import ApiErrors, { APIError } from '~apiErrors'; import HttpStatusCode from '#types/HttpStatusCode'; +import ApiErrors, { APIError } from '~apiErrors'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; import { ZodError } from 'zod'; @@ -135,7 +135,11 @@ export function errorHandler( .status(HttpStatusCode.NOT_FOUND_404) .send(ApiErrors.RESOURCE_NOT_FOUND); break; - + case 'P2025': + res + .status(HttpStatusCode.NOT_FOUND_404) + .send(ApiErrors.RESOURCE_NOT_FOUND); + break; default: res .status(HttpStatusCode.INTERNAL_SERVER_ERROR_500) diff --git a/back/src/types/dto/articles/OneArticleDTO.ts b/back/src/types/dto/articles/OneArticleDTO.ts deleted file mode 100644 index b6b819b..0000000 --- a/back/src/types/dto/articles/OneArticleDTO.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod'; - -const OneArticleDTO = z.object({ - id: z.coerce.number(), -}); - -export default OneArticleDTO; diff --git a/back/src/types/dto/urlParamIdDTO.ts b/back/src/types/dto/urlParamIdDTO.ts new file mode 100644 index 0000000..fa6898b --- /dev/null +++ b/back/src/types/dto/urlParamIdDTO.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +const urlParamIdDTO = z.object({ + id: z.coerce.number(), +}); + +export default urlParamIdDTO;