Updated dependencies, added and ran prettier

This commit is contained in:
Florian Sylvain
2023-02-23 10:08:35 +01:00
parent 8c0549226c
commit 53795764dc
12 changed files with 10799 additions and 9383 deletions
+10
View File
@@ -0,0 +1,10 @@
mysql-data
node_modules
dist
coverage
.env
.vscode
.idea
+6
View File
@@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": false,
"printWidth": 100
}
+2412 -1052
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -5,6 +5,8 @@
"main": "app.js", "main": "app.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"prettier:fix": "prettier --write ./src",
"prettier:check": "prettier --check ./src",
"build": "tsc", "build": "tsc",
"build:dev": "tsc -w", "build:dev": "tsc -w",
"start": "concurrently npm:build \"wait-on dist/index.js && node dist/index.js\"", "start": "concurrently npm:build \"wait-on dist/index.js && node dist/index.js\"",
@@ -23,6 +25,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"nodemon": "^2.0.20", "nodemon": "^2.0.20",
"prisma": "^4.10.1",
"wait-on": "^7.0.1", "wait-on": "^7.0.1",
"zod": "^3.20.2" "zod": "^3.20.2"
}, },
@@ -35,10 +38,9 @@
"@types/jest": "^29.4.0", "@types/jest": "^29.4.0",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"install": "^0.13.0",
"jest": "^29.4.1", "jest": "^29.4.1",
"npm": "^9.4.0", "npm": "^9.4.0",
"prisma": "^4.10.1", "prettier": "^2.8.4",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
+176 -159
View File
@@ -1,7 +1,7 @@
import { initServer } from '../app.js' import { initServer } from "../app.js"
import request from 'supertest' import request from "supertest"
import { PrismaClient } from '@prisma/client' import { PrismaClient } from "@prisma/client"
import bcrypt from 'bcrypt' import bcrypt from "bcrypt"
const prisma = new PrismaClient() const prisma = new PrismaClient()
const app = initServer() const app = initServer()
@@ -9,7 +9,7 @@ let jwtCookie: string | undefined
const testUserCredentials = { const testUserCredentials = {
email: "a@a.com", email: "a@a.com",
password: "aaaaaaaaaa" password: "aaaaaaaaaa",
} }
let category_id: number let category_id: number
@@ -20,186 +20,197 @@ beforeAll(async () => {
data: { data: {
email: testUserCredentials.email, email: testUserCredentials.email,
password: await bcrypt.hash(testUserCredentials.password, 10), password: await bcrypt.hash(testUserCredentials.password, 10),
name: '', name: "",
picture_path: '' picture_path: "",
} },
}) })
}) })
afterAll(async () => { afterAll(async () => {
await prisma.user.delete({ await prisma.user.delete({
where: { email: testUserCredentials.email } where: { email: testUserCredentials.email },
}) })
await prisma.user.delete({ await prisma.user.delete({
where: { email: 'b@b.com' } where: { email: "b@b.com" },
}) })
}) })
describe('GET /v1', () => { describe("GET /v1", () => {
it('returns status code 200 and api version message', async () => { it("returns status code 200 and api version message", async () => {
const res = await request(app) const res = await request(app).get("/v1")
.get('/v1')
expect(res.statusCode).toEqual(200) expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('POST /v1/session/register', () => { describe("POST /v1/session/register", () => {
it('returns status code 200 and a success message', async () => { it("returns status code 200 and a success message", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/session/register') .post("/v1/session/register")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.send(JSON.stringify({ .send(
email: 'b@b.com', JSON.stringify({
password: 'bbbbbbbbbbb' email: "b@b.com",
})) password: "bbbbbbbbbbb",
})
)
expect(res.statusCode).toEqual(200) expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
it('returns status code 400 and an error message', async () => { it("returns status code 400 and an error message", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/session/register') .post("/v1/session/register")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.send(JSON.stringify(testUserCredentials)) .send(JSON.stringify(testUserCredentials))
expect(res.statusCode).toEqual(400) expect(res.statusCode).toEqual(400)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('POST /v1/session/login', () => { describe("POST /v1/session/login", () => {
it('returns status code 200 and set httpOnly jwt token cookie', async () => { it("returns status code 200 and set httpOnly jwt token cookie", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/session/login') .post("/v1/session/login")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.send(JSON.stringify(testUserCredentials)) .send(JSON.stringify(testUserCredentials))
const jwtRegEx = /^jwt=.*Path=\/.*HttpOnly.*Secure.*SameSite=Strict.*$/ const jwtRegEx = /^jwt=.*Path=\/.*HttpOnly.*Secure.*SameSite=Strict.*$/
jwtCookie = res.get('Set-Cookie') jwtCookie = res.get("Set-Cookie")?.filter((cookie) => cookie.match(jwtRegEx))[0]
?.filter(cookie => cookie.match(jwtRegEx))[0]
expect(res.statusCode).toEqual(200) expect(res.statusCode).toEqual(200)
expect(jwtCookie).not.toBe(undefined) expect(jwtCookie).not.toBe(undefined)
}) })
it('returns status code 400 and credentials error message', async () => { it("returns status code 400 and credentials error message", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/session/login') .post("/v1/session/login")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.send(JSON.stringify({ .send(
email: 'rip bozo', JSON.stringify({
password: 'rip bozo' email: "rip bozo",
})) password: "rip bozo",
})
)
expect(res.statusCode).toEqual(400) expect(res.statusCode).toEqual(400)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('POST /v1/category', () => { describe("POST /v1/category", () => {
it('returns status code 200 and success message', async () => { it("returns status code 200 and success message", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/category') .post("/v1/category")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
name: 'VueJS Composition API' JSON.stringify({
})) name: "VueJS Composition API",
})
)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
it('returns status code 400 and error message', async () => { it("returns status code 400 and error message", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/category') .post("/v1/category")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
pouet: 'VueJS Composition API' JSON.stringify({
})) pouet: "VueJS Composition API",
})
)
expect(res.status).toEqual(400) expect(res.status).toEqual(400)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('GET /v1/category/', () => { describe("GET /v1/category/", () => {
it('returns status code 200 and the categories', async () => { it("returns status code 200 and the categories", async () => {
const res = await request(app) const res = await request(app)
.get('/v1/category') .get("/v1/category")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
category_id = res.body.categories[0].id category_id = res.body.categories[0].id
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.categories[0].name).toEqual('VueJS Composition API') expect(res.body.categories[0].name).toEqual("VueJS Composition API")
}) })
it('returns status code 200 and no categories', async () => { it("returns status code 200 and no categories", async () => {
const res = await request(app) const res = await request(app)
.get('/v1/category?skip=5&take=5') .get("/v1/category?skip=5&take=5")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.links.next).toEqual('/v1/category?start=10&per_page=5') expect(res.body.links.next).toEqual("/v1/category?start=10&per_page=5")
expect(res.body.links.prev).toEqual('/v1/category?start=0&per_page=5') expect(res.body.links.prev).toEqual("/v1/category?start=0&per_page=5")
}) })
}) })
describe('GET /v1/category/:id', () => { describe("GET /v1/category/:id", () => {
it('returns status code 200 and the categories', async () => { it("returns status code 200 and the categories", async () => {
const res = await request(app) const res = await request(app)
.get(`/v1/category/${category_id}`) .get(`/v1/category/${category_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.category.name).toEqual('VueJS Composition API') expect(res.body.category.name).toEqual("VueJS Composition API")
}) })
}) })
describe('PUT /v1/category/:id', () => { describe("PUT /v1/category/:id", () => {
it('returns status code 200 and a success message', async () => { it("returns status code 200 and a success message", async () => {
const res = await request(app) const res = await request(app)
.put(`/v1/category/${category_id}`) .put(`/v1/category/${category_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
name: 'VueJS v3 Composition API' JSON.stringify({
})) name: "VueJS v3 Composition API",
})
)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
it('returns status code 400 and an error message', async () => { it("returns status code 400 and an error message", async () => {
const res = await request(app) const res = await request(app)
.put(`/v1/category/${23591}`) .put(`/v1/category/${23591}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
pouet: 'salut' JSON.stringify({
})) pouet: "salut",
})
)
expect(res.status).toEqual(400) expect(res.status).toEqual(400)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('POST /v1/snippet', () => { describe("POST /v1/snippet", () => {
it('returns status code 200 and the snippets', async () => { it("returns status code 200 and the snippets", async () => {
const res = await request(app) const res = await request(app)
.post('/v1/snippet') .post("/v1/snippet")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
title: 'Vue3 CompAPI TS script-template-style', JSON.stringify({
title: "Vue3 CompAPI TS script-template-style",
code: `<script setup lang="ts"> code: `<script setup lang="ts">
</script> </script>
@@ -208,124 +219,130 @@ describe('POST /v1/snippet', () => {
<style scoped> <style scoped>
</style>`, </style>`,
language: 'vue', language: "vue",
tags: ['template', 'vuejs'], tags: ["template", "vuejs"],
category_id category_id,
})) })
)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('GET /v1/snippet/', () => { describe("GET /v1/snippet/", () => {
it('returns status code 200 and all snippets', async () => { it("returns status code 200 and all snippets", async () => {
const res = await request(app) const res = await request(app)
.get('/v1/snippet') .get("/v1/snippet")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
snippet_id = res.body.snippets[0].id snippet_id = res.body.snippets[0].id
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.snippets[0].title).toEqual('Vue3 CompAPI TS script-template-style') expect(res.body.snippets[0].title).toEqual("Vue3 CompAPI TS script-template-style")
expect(res.body.snippets[0].category_id).toEqual(category_id) expect(res.body.snippets[0].category_id).toEqual(category_id)
}) })
it('returns status code 200 and no snippets', async () => { it("returns status code 200 and no snippets", async () => {
const res = await request(app) const res = await request(app)
.get('/v1/snippet?skip=5&take=5') .get("/v1/snippet?skip=5&take=5")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.links.next).toEqual('/v1/snippet?start=10&per_page=5') expect(res.body.links.next).toEqual("/v1/snippet?start=10&per_page=5")
expect(res.body.links.prev).toEqual('/v1/snippet?start=0&per_page=5') expect(res.body.links.prev).toEqual("/v1/snippet?start=0&per_page=5")
}) })
}) })
describe('PUT /v1/snippet/:id', () => { describe("PUT /v1/snippet/:id", () => {
it('returns status code 200 and success message', async () => { it("returns status code 200 and success message", async () => {
const res = await request(app) const res = await request(app)
.put(`/v1/snippet/${snippet_id}`) .put(`/v1/snippet/${snippet_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
code: '<p>en fait non à vuejs</p>', JSON.stringify({
language: 'html', code: "<p>en fait non à vuejs</p>",
tags: ['pouet', 'pouet', 'pouet'] language: "html",
})) tags: ["pouet", "pouet", "pouet"],
})
)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe("GET /v1/snippet/:id", () => {
describe('GET /v1/snippet/:id', () => { it("returns status code 200 and one snippet", async () => {
it('returns status code 200 and one snippet', async () => {
const res = await request(app) const res = await request(app)
.get(`/v1/snippet/${snippet_id}`) .get(`/v1/snippet/${snippet_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body.snippet.tags.length).toEqual(3) expect(res.body.snippet.tags.length).toEqual(3)
expect(res.body.snippet.tags).not.toContain('vuejs') expect(res.body.snippet.tags).not.toContain("vuejs")
expect(res.body.snippet.category_id).toEqual(category_id) expect(res.body.snippet.category_id).toEqual(category_id)
}) })
}) })
describe('DELETE /v1/snippet/:id', () => { describe("DELETE /v1/snippet/:id", () => {
it('returns status code 200 and success message', async () => { it("returns status code 200 and success message", async () => {
const res = await request(app) const res = await request(app)
.delete(`/v1/snippet/${snippet_id}`) .delete(`/v1/snippet/${snippet_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('DELETE /v1/category/:id', () => { describe("DELETE /v1/category/:id", () => {
it('returns status code 200 and a success message', async () => { it("returns status code 200 and a success message", async () => {
const res = await request(app) const res = await request(app)
.delete(`/v1/category/${category_id}`) .delete(`/v1/category/${category_id}`)
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
expect(res.status).toEqual(200) expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
describe('PUT /v1/user', () => { describe("PUT /v1/user", () => {
it('returns status code 200 and success message', async () => { it("returns status code 200 and success message", async () => {
const res = await request(app) const res = await request(app)
.put('/v1/user') .put("/v1/user")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
JSON.stringify({
id: 1234, id: 1234,
name: 'didier', name: "didier",
pouet: 'alo' pouet: "alo",
})) })
)
expect(res.statusCode).toEqual(200) expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
it('returns status code 400 and error message', async () => { it("returns status code 400 and error message", async () => {
const res = await request(app) const res = await request(app)
.put('/v1/user') .put("/v1/user")
.set('Content-Type', 'application/json') .set("Content-Type", "application/json")
.set('Cookie', jwtCookie as string) .set("Cookie", jwtCookie as string)
.send(JSON.stringify({ .send(
picture_path: 424242 JSON.stringify({
})) picture_path: 424242,
})
)
expect(res.statusCode).toEqual(400) expect(res.statusCode).toEqual(400)
expect(res.body).toHaveProperty('message') expect(res.body).toHaveProperty("message")
}) })
}) })
+23 -23
View File
@@ -1,27 +1,27 @@
import express, { RequestHandler } from 'express' import express, { RequestHandler } from "express"
import * as dotenv from 'dotenv' import * as dotenv from "dotenv"
import jwt from 'jsonwebtoken' import jwt from "jsonwebtoken"
import cors from 'cors' import cors from "cors"
import cookieParser from 'cookie-parser' import cookieParser from "cookie-parser"
import { getJwtSecret } from './utils/jwt.js' import { getJwtSecret } from "./utils/jwt.js"
import userRouter from './routers/user.js' import userRouter from "./routers/user.js"
import sessionRouter from './routers/session.js' import sessionRouter from "./routers/session.js"
import categoryRouter from './routers/category.js' import categoryRouter from "./routers/category.js"
import snippetRouter from './routers/snippet.js' import snippetRouter from "./routers/snippet.js"
const authMiddleware: RequestHandler = (req, res, next) => { const authMiddleware: RequestHandler = (req, res, next) => {
const token = req.cookies.jwt const token = req.cookies.jwt
if (token == undefined) { if (token == undefined) {
res.status(400).send({ message: 'Cannot find jwt auth cookie.' }) res.status(400).send({ message: "Cannot find jwt auth cookie." })
return; return
} }
try { try {
jwt.verify(token, getJwtSecret()) jwt.verify(token, getJwtSecret())
} catch (error) { } catch (error) {
res.status(400).send({ message: 'Incorrect JWT.', error }) res.status(400).send({ message: "Incorrect JWT.", error })
return; return
} }
next() next()
@@ -33,12 +33,12 @@ const appRouterGet: RequestHandler = (req, res) => {
function initEnvVariables(): void { function initEnvVariables(): void {
dotenv.config() dotenv.config()
const varsToCheck = ['API_PORT', 'FRONT_ORIGIN'] const varsToCheck = ["API_PORT", "FRONT_ORIGIN"]
varsToCheck.forEach(varName => { varsToCheck.forEach((varName) => {
const variable = process.env[varName] const variable = process.env[varName]
if (variable == undefined || variable === '') { if (variable == undefined || variable === "") {
console.error(`Missing '${varName}' in .env`) console.error(`Missing '${varName}' in .env`)
process.exit() process.exit()
} }
@@ -56,14 +56,14 @@ export function initServer(): express.Express {
app.use(express.json()) app.use(express.json())
app.use(express.urlencoded({ extended: false })) app.use(express.urlencoded({ extended: false }))
appRouter.get('/', appRouterGet) appRouter.get("/", appRouterGet)
appRouter.use('/session/', sessionRouter) appRouter.use("/session/", sessionRouter)
appRouter.use('/user/', authMiddleware, userRouter) appRouter.use("/user/", authMiddleware, userRouter)
appRouter.use('/category/', authMiddleware, categoryRouter) appRouter.use("/category/", authMiddleware, categoryRouter)
appRouter.use('/snippet/', authMiddleware, snippetRouter) appRouter.use("/snippet/", authMiddleware, snippetRouter)
app.use('/v1/', appRouter) app.use("/v1/", appRouter)
return app return app
} }
+42 -36
View File
@@ -7,22 +7,28 @@ import { userIdMiddleware } from "./user.js"
const categoryRouter = express.Router() const categoryRouter = express.Router()
const prisma = new PrismaClient() const prisma = new PrismaClient()
const categoryPostParser = z.object({ const categoryPostParser = z
name: z.string().max(50) .object({
}).required() name: z.string().max(50),
})
.required()
const categoryUpdateParser = z.object({ const categoryUpdateParser = z
name: z.string().max(50) .object({
}).required() name: z.string().max(50),
})
.required()
const paramsIdParser = z.object({ const paramsIdParser = z
.object({
id: z.coerce.number(), id: z.coerce.number(),
}).required() })
.required()
async function findCategories(userId: number, pagination: Pagination): Promise<Category[]> { async function findCategories(userId: number, pagination: Pagination): Promise<Category[]> {
return await prisma.category.findMany({ return await prisma.category.findMany({
where: { user_id: userId }, where: { user_id: userId },
...pagination ...pagination,
}) })
} }
@@ -33,15 +39,15 @@ const categoryGet: RequestHandler = async (req, res) => {
try { try {
categories = await findCategories(req.body.userId, pagination) categories = await findCategories(req.body.userId, pagination)
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ res.json({
categories, categories,
pagination, pagination,
links: getPaginationLinks(pagination, 'category'), links: getPaginationLinks(pagination, "category"),
total: categories.length total: categories.length,
}) })
} }
@@ -52,12 +58,12 @@ const categoryGetUnique: RequestHandler = async (req, res) => {
category = await prisma.category.findFirst({ category = await prisma.category.findFirst({
where: { where: {
id: paramsIdParser.parse(req.params).id, id: paramsIdParser.parse(req.params).id,
user_id: req.body.userId user_id: req.body.userId,
} },
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ category }) res.json({ category })
@@ -69,15 +75,15 @@ const categoryPost: RequestHandler = async (req, res) => {
await prisma.category.create({ await prisma.category.create({
data: { data: {
name: newCategory.name, name: newCategory.name,
user_id: req.body.userId user_id: req.body.userId,
} },
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: 'Category successfully added.' }) res.json({ message: "Category successfully added." })
} }
const categoryUpdate: RequestHandler = async (req, res) => { const categoryUpdate: RequestHandler = async (req, res) => {
@@ -88,15 +94,15 @@ const categoryUpdate: RequestHandler = async (req, res) => {
updated = await prisma.category.updateMany({ updated = await prisma.category.updateMany({
where: { where: {
id: paramsIdParser.parse(req.params).id, id: paramsIdParser.parse(req.params).id,
user_id: req.body.userId user_id: req.body.userId,
}, },
data: { data: {
name: categoryToUpdate.name name: categoryToUpdate.name,
} },
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: `${updated.count} category / categories successfully updated.` }) res.json({ message: `${updated.count} category / categories successfully updated.` })
@@ -109,21 +115,21 @@ const categoryDelete: RequestHandler = async (req, res) => {
deleted = await prisma.category.deleteMany({ deleted = await prisma.category.deleteMany({
where: { where: {
id: paramsIdParser.parse(req.params).id, id: paramsIdParser.parse(req.params).id,
user_id: req.body.userId user_id: req.body.userId,
} },
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: `${deleted.count} category / categories successfully deleted.` }) res.json({ message: `${deleted.count} category / categories successfully deleted.` })
} }
categoryRouter.get('/', userIdMiddleware, categoryGet) categoryRouter.get("/", userIdMiddleware, categoryGet)
categoryRouter.get('/:id', userIdMiddleware, categoryGetUnique) categoryRouter.get("/:id", userIdMiddleware, categoryGetUnique)
categoryRouter.post('/', userIdMiddleware, categoryPost) categoryRouter.post("/", userIdMiddleware, categoryPost)
categoryRouter.put('/:id', userIdMiddleware, categoryUpdate) categoryRouter.put("/:id", userIdMiddleware, categoryUpdate)
categoryRouter.delete('/:id', userIdMiddleware, categoryDelete) categoryRouter.delete("/:id", userIdMiddleware, categoryDelete)
export default categoryRouter export default categoryRouter
+22 -22
View File
@@ -3,11 +3,11 @@ import express from "express"
import { RequestHandler } from "express-serve-static-core" import { RequestHandler } from "express-serve-static-core"
import { z } from "zod" import { z } from "zod"
import { getJwtSecret } from "../utils/jwt.js" import { getJwtSecret } from "../utils/jwt.js"
import bcrypt from 'bcrypt' import bcrypt from "bcrypt"
import jwt from 'jsonwebtoken' import jwt from "jsonwebtoken"
interface LoginData { interface LoginData {
email: string, email: string
password: string password: string
} }
@@ -16,7 +16,7 @@ const prisma = new PrismaClient()
const LoginValidator = z.object({ const LoginValidator = z.object({
email: z.string().email(), email: z.string().email(),
password: z.string().min(4).max(20) password: z.string().min(4).max(20),
}) })
async function isUserValid(user: User | null, loginData: any): Promise<boolean> { async function isUserValid(user: User | null, loginData: any): Promise<boolean> {
@@ -34,10 +34,10 @@ async function createUser(email: string, password: string) {
data: { data: {
email: email, email: email,
password: password, password: password,
name: '', name: "",
picture_path: '', picture_path: "",
created_at: new Date(), created_at: new Date(),
updated_at: new Date() updated_at: new Date(),
}, },
}) })
} }
@@ -57,44 +57,44 @@ function parseLoginData(data: any): LoginData | undefined {
export const userRouterPostLogin: RequestHandler = async (req, res) => { export const userRouterPostLogin: RequestHandler = async (req, res) => {
const loginData = parseLoginData(req.body) const loginData = parseLoginData(req.body)
if (loginData === undefined) { if (loginData === undefined) {
res.status(400).json({ message: 'Incorrect credentials format.' }) res.status(400).json({ message: "Incorrect credentials format." })
return; return
} }
const user = await getUserByEmail(loginData.email) const user = await getUserByEmail(loginData.email)
if (!await isUserValid(user, loginData)) { if (!(await isUserValid(user, loginData))) {
res.status(400).json({ message: 'Incorrect credentials.' }) res.status(400).json({ message: "Incorrect credentials." })
return; return
} }
const jwtToken = jwt.sign({ userId: user?.id }, getJwtSecret(), { expiresIn: "1h" }) const jwtToken = jwt.sign({ userId: user?.id }, getJwtSecret(), { expiresIn: "1h" })
res.cookie('jwt', jwtToken, { res.cookie("jwt", jwtToken, {
httpOnly: true, httpOnly: true,
secure: true, secure: true,
sameSite: 'strict' sameSite: "strict",
}).json({ message: 'Logged in! httpOnly cookie set.' }) }).json({ message: "Logged in! httpOnly cookie set." })
} }
export const userRouterPostRegister: RequestHandler = async (req, res) => { export const userRouterPostRegister: RequestHandler = async (req, res) => {
const loginData = parseLoginData(req.body) const loginData = parseLoginData(req.body)
if (loginData == undefined) { if (loginData == undefined) {
res.status(400).json({ message: 'Incorrect credentials format.' }) res.status(400).json({ message: "Incorrect credentials format." })
return; return
} }
if (await isUserEmailAlreadyUsed(loginData.email)) { if (await isUserEmailAlreadyUsed(loginData.email)) {
res.status(400).json({ message: 'Email already linked to an account.' }) res.status(400).json({ message: "Email already linked to an account." })
return; return
} }
const hashedPassword = await bcrypt.hash(loginData.password, 10) const hashedPassword = await bcrypt.hash(loginData.password, 10)
await createUser(loginData.email, hashedPassword) await createUser(loginData.email, hashedPassword)
res.json({ message: 'User successfully created!' }) res.json({ message: "User successfully created!" })
} }
sessionRouter.post('/login', userRouterPostLogin) sessionRouter.post("/login", userRouterPostLogin)
sessionRouter.post('/register', userRouterPostRegister) sessionRouter.post("/register", userRouterPostRegister)
export default sessionRouter export default sessionRouter
+63 -50
View File
@@ -7,24 +7,28 @@ import { userIdMiddleware } from "./user.js"
const snippetRouter = express.Router() const snippetRouter = express.Router()
const prisma = new PrismaClient() const prisma = new PrismaClient()
const snippetPostParser = z.object({ const snippetPostParser = z
.object({
code: z.string(), code: z.string(),
title: z.string().max(50), title: z.string().max(50),
language: z.string().max(32), language: z.string().max(32),
tags: z.array(z.string()), tags: z.array(z.string()),
category_id: z.number() category_id: z.number(),
}).required() })
.required()
const snippetUpdateParser = z.object({ const snippetUpdateParser = z.object({
code: z.string().optional(), code: z.string().optional(),
title: z.string().max(50).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() tags: z.array(z.string()).optional(),
}) })
const paramsIdParser = z.object({ const paramsIdParser = z
.object({
id: z.coerce.number(), id: z.coerce.number(),
}).required() })
.required()
function shortenSnippetsTagDepth(snippets: Snippet[]) { function shortenSnippetsTagDepth(snippets: Snippet[]) {
snippets.forEach((snippet: any) => { snippets.forEach((snippet: any) => {
@@ -39,9 +43,9 @@ async function findSnippets(userId: number, pagination: Pagination): Promise<Sni
include: { include: {
Snippet_tag: { Snippet_tag: {
select: { tag: { select: { id: true, name: true } } }, select: { tag: { select: { id: true, name: true } } },
}
}, },
...pagination },
...pagination,
}) })
} }
@@ -53,15 +57,15 @@ const snippetGet: RequestHandler = async (req, res) => {
snippets = await findSnippets(req.body.userId, pagination) snippets = await findSnippets(req.body.userId, pagination)
shortenSnippetsTagDepth(snippets) shortenSnippetsTagDepth(snippets)
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ res.json({
snippets, snippets,
pagination, pagination,
links: getPaginationLinks(pagination, 'snippet'), links: getPaginationLinks(pagination, "snippet"),
total: snippets.length total: snippets.length,
}) })
} }
@@ -72,20 +76,20 @@ const snippetGetUnique: RequestHandler = async (req, res) => {
snippet = await prisma.snippet.findFirst({ snippet = await prisma.snippet.findFirst({
where: { where: {
id: paramsIdParser.parse(req.params).id, id: paramsIdParser.parse(req.params).id,
user_id: req.body.userId user_id: req.body.userId,
}, },
include: { include: {
Snippet_tag: { Snippet_tag: {
select: { tag: { select: { id: true, name: true } } }, select: { tag: { select: { id: true, name: true } } },
} },
} },
}) })
if (snippet != undefined) { if (snippet != undefined) {
shortenSnippetsTagDepth([snippet]) shortenSnippetsTagDepth([snippet])
} }
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ snippet }) res.json({ snippet })
@@ -95,8 +99,8 @@ async function deleteSnippetTags(userId: number, snippetId: number): Promise<voi
await prisma.snippet_tag.deleteMany({ await prisma.snippet_tag.deleteMany({
where: { where: {
tag: { user_id: userId }, tag: { user_id: userId },
snippet_id: snippetId snippet_id: snippetId,
} },
}) })
} }
@@ -104,35 +108,44 @@ async function createSnippetTag(snippetId: number, userId: number, tagName: stri
await prisma.snippet_tag.create({ await prisma.snippet_tag.create({
data: { data: {
snippet: { snippet: {
connect: { id: snippetId } connect: { id: snippetId },
}, },
tag: { tag: {
create: { create: {
name: tagName, name: tagName,
user_id: userId user_id: userId,
} },
} },
} },
}) })
} }
async function updateSnippet(snippetId: number, userId: number, languageId: number | undefined, snippetData: any): Promise<Prisma.BatchPayload> { async function updateSnippet(
snippetId: number,
userId: number,
languageId: number | undefined,
snippetData: any
): Promise<Prisma.BatchPayload> {
return await prisma.snippet.updateMany({ return await prisma.snippet.updateMany({
where: { where: {
id: snippetId, id: snippetId,
user_id: userId user_id: userId,
}, },
data: { data: {
title: snippetData.title, title: snippetData.title,
code: snippetData.code, code: snippetData.code,
user_id: userId, user_id: userId,
language_id: languageId, language_id: languageId,
} },
}) })
} }
async function updateTagsFromSnippet(snippetId: number, userId: number, snippetData: any): Promise<void> { async function updateTagsFromSnippet(
if (snippetData.tags == undefined) return; snippetId: number,
userId: number,
snippetData: any
): Promise<void> {
if (snippetData.tags == undefined) return
await deleteSnippetTags(userId, snippetId) await deleteSnippetTags(userId, snippetId)
@@ -149,28 +162,28 @@ const snippetPost: RequestHandler = async (req, res) => {
title: newSnippet.title, title: newSnippet.title,
code: newSnippet.code, code: newSnippet.code,
user: { user: {
connect: { id: req.body.userId } connect: { id: req.body.userId },
}, },
language: { language: {
connectOrCreate: { connectOrCreate: {
where: { name: newSnippet.language }, where: { name: newSnippet.language },
create: { name: newSnippet.language } create: { name: newSnippet.language },
} },
}, },
category: { category: {
connect: { id: newSnippet.category_id } connect: { id: newSnippet.category_id },
} },
} },
}) })
for (const tag of newSnippet.tags) { for (const tag of newSnippet.tags) {
await createSnippetTag(snippet.id, req.body.userId, tag) await createSnippetTag(snippet.id, req.body.userId, tag)
} }
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: 'Snippet successfully added.' }) res.json({ message: "Snippet successfully added." })
} }
const snippetUpdate: RequestHandler = async (req, res) => { const snippetUpdate: RequestHandler = async (req, res) => {
@@ -179,14 +192,14 @@ const snippetUpdate: RequestHandler = async (req, res) => {
try { try {
const snippetData = snippetUpdateParser.parse(req.body) const snippetData = snippetUpdateParser.parse(req.body)
const languageId = await prisma.language.findFirst({ const languageId = await prisma.language.findFirst({
where: { name: snippetData.language } where: { name: snippetData.language },
}) })
const snippetId = paramsIdParser.parse(req.params).id const snippetId = paramsIdParser.parse(req.params).id
updated = await updateSnippet(snippetId, req.body.userId, languageId?.id, snippetData) updated = await updateSnippet(snippetId, req.body.userId, languageId?.id, snippetData)
await updateTagsFromSnippet(snippetId, req.body.userId, snippetData) await updateTagsFromSnippet(snippetId, req.body.userId, snippetData)
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: `${updated.count} snippet / categories successfully updated.` }) res.json({ message: `${updated.count} snippet / categories successfully updated.` })
@@ -199,21 +212,21 @@ const snippetDelete: RequestHandler = async (req, res) => {
deleted = await prisma.category.deleteMany({ deleted = await prisma.category.deleteMany({
where: { where: {
id: paramsIdParser.parse(req.params).id, id: paramsIdParser.parse(req.params).id,
user_id: req.body.userId user_id: req.body.userId,
} },
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: `${deleted.count} snippet(s) successfully deleted.` }) res.json({ message: `${deleted.count} snippet(s) successfully deleted.` })
} }
snippetRouter.get('/', userIdMiddleware, snippetGet) snippetRouter.get("/", userIdMiddleware, snippetGet)
snippetRouter.get('/:id', userIdMiddleware, snippetGetUnique) snippetRouter.get("/:id", userIdMiddleware, snippetGetUnique)
snippetRouter.post('/', userIdMiddleware, snippetPost) snippetRouter.post("/", userIdMiddleware, snippetPost)
snippetRouter.put('/:id', userIdMiddleware, snippetUpdate) snippetRouter.put("/:id", userIdMiddleware, snippetUpdate)
snippetRouter.delete('/:id', userIdMiddleware, snippetDelete) snippetRouter.delete("/:id", userIdMiddleware, snippetDelete)
export default snippetRouter export default snippetRouter
+14 -14
View File
@@ -1,21 +1,21 @@
import { PrismaClient } from '@prisma/client' import { PrismaClient } from "@prisma/client"
import express, { RequestHandler } from 'express' import express, { RequestHandler } from "express"
import jwt from 'jsonwebtoken' import jwt from "jsonwebtoken"
import { z } from 'zod' import { z } from "zod"
const userRouter = express.Router() const userRouter = express.Router()
const prisma = new PrismaClient() const prisma = new PrismaClient()
const editableUserData = z.object({ const editableUserData = z.object({
name: z.string().optional(), name: z.string().optional(),
picture_path: z.string().optional() picture_path: z.string().optional(),
}) })
export function parseJwtUserId(jwtoken: string): number | undefined { export function parseJwtUserId(jwtoken: string): number | undefined {
const payload = jwt.decode(jwtoken) const payload = jwt.decode(jwtoken)
if (payload == undefined) { if (payload == undefined) {
return undefined return undefined
} else if (typeof payload == 'string') { } else if (typeof payload == "string") {
return undefined return undefined
} }
return payload.userId return payload.userId
@@ -24,8 +24,8 @@ export function parseJwtUserId(jwtoken: string): number | undefined {
export const userIdMiddleware: RequestHandler = (req, res, next) => { export const userIdMiddleware: RequestHandler = (req, res, next) => {
const userId = parseJwtUserId(req.cookies.jwt) const userId = parseJwtUserId(req.cookies.jwt)
if (userId === undefined) { if (userId === undefined) {
res.status(400).json({ message: 'Incorrect JWT payload.' }) res.status(400).json({ message: "Incorrect JWT payload." })
return; return
} }
req.body.userId = userId req.body.userId = userId
next() next()
@@ -34,21 +34,21 @@ export const userIdMiddleware: RequestHandler = (req, res, next) => {
const userRouterPut: RequestHandler = async (req, res) => { const userRouterPut: RequestHandler = async (req, res) => {
const userId = parseJwtUserId(req.cookies.jwt) const userId = parseJwtUserId(req.cookies.jwt)
if (userId === undefined) { if (userId === undefined) {
res.status(400).json({ message: 'Incorrect JWT payload.' }) res.status(400).json({ message: "Incorrect JWT payload." })
return; return
} }
try { try {
await prisma.user.update({ await prisma.user.update({
where: { id: userId }, where: { id: userId },
data: editableUserData.parse(req.body) data: editableUserData.parse(req.body),
}) })
} catch (error: any) { } catch (error: any) {
res.status(400).json({ message: (error.issues ?? error) }) res.status(400).json({ message: error.issues ?? error })
return; return
} }
res.json({ message: 'User successfully updated.' }) res.json({ message: "User successfully updated." })
} }
userRouter.put("/", userRouterPut) userRouter.put("/", userRouterPut)
+5 -3
View File
@@ -1,18 +1,20 @@
import { z } from "zod" import { z } from "zod"
export interface Pagination { export interface Pagination {
skip: number, skip: number
take: number take: number
} }
export const queryPaginationParser = z.object({ export const queryPaginationParser = z.object({
skip: z.coerce.number().optional().default(0), skip: z.coerce.number().optional().default(0),
take: z.coerce.number().optional().default(10) take: z.coerce.number().optional().default(10),
}) })
export function getPaginationLinks(query: Pagination, routeName: string): object { export function getPaginationLinks(query: Pagination, routeName: string): object {
return { return {
next: `/v1/${routeName}?start=${query.skip + query.take}&per_page=${query.take}`, next: `/v1/${routeName}?start=${query.skip + query.take}&per_page=${query.take}`,
prev: `/v1/${routeName}?start=${Math.max(0, query.skip - query.take)}&per_page=${query.take}` prev: `/v1/${routeName}?start=${Math.max(0, query.skip - query.take)}&per_page=${
query.take
}`,
} }
} }