mirror of
https://github.com/Floriansylvain/SnippetsManager.git
synced 2026-08-19 11:43:15 +02:00
Updated dependencies, added and ran prettier
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
mysql-data
|
||||
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
|
||||
.env
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": false,
|
||||
"printWidth": 100
|
||||
}
|
||||
Generated
+10046
-8686
File diff suppressed because it is too large
Load Diff
+47
-45
@@ -1,47 +1,49 @@
|
||||
{
|
||||
"name": "snippetsmanager",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:dev": "tsc -w",
|
||||
"start": "concurrently npm:build \"wait-on dist/index.js && node dist/index.js\"",
|
||||
"dev": "concurrently npm:build:dev \"wait-on dist/index.js && nodemon dist/index.js\"",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.10.1",
|
||||
"bcrypt": "^5.1.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"wait-on": "^7.0.1",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/express": "^4.17.16",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"install": "^0.13.0",
|
||||
"jest": "^29.4.1",
|
||||
"npm": "^9.4.0",
|
||||
"prisma": "^4.10.1",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
"name": "snippetsmanager",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prettier:fix": "prettier --write ./src",
|
||||
"prettier:check": "prettier --check ./src",
|
||||
"build": "tsc",
|
||||
"build:dev": "tsc -w",
|
||||
"start": "concurrently npm:build \"wait-on dist/index.js && node dist/index.js\"",
|
||||
"dev": "concurrently npm:build:dev \"wait-on dist/index.js && nodemon dist/index.js\"",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.10.1",
|
||||
"bcrypt": "^5.1.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"prisma": "^4.10.1",
|
||||
"wait-on": "^7.0.1",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/express": "^4.17.16",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"jest": "^29.4.1",
|
||||
"npm": "^9.4.0",
|
||||
"prettier": "^2.8.4",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
+266
-249
@@ -1,206 +1,217 @@
|
||||
import { initServer } from '../app.js'
|
||||
import request from 'supertest'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { initServer } from "../app.js"
|
||||
import request from "supertest"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
import bcrypt from "bcrypt"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
const app = initServer()
|
||||
let jwtCookie: string | undefined
|
||||
|
||||
const testUserCredentials = {
|
||||
email: "a@a.com",
|
||||
password: "aaaaaaaaaa"
|
||||
email: "a@a.com",
|
||||
password: "aaaaaaaaaa",
|
||||
}
|
||||
|
||||
let category_id: number
|
||||
let snippet_id: number
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: testUserCredentials.email,
|
||||
password: await bcrypt.hash(testUserCredentials.password, 10),
|
||||
name: '',
|
||||
picture_path: ''
|
||||
}
|
||||
})
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: testUserCredentials.email,
|
||||
password: await bcrypt.hash(testUserCredentials.password, 10),
|
||||
name: "",
|
||||
picture_path: "",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.user.delete({
|
||||
where: { email: testUserCredentials.email }
|
||||
})
|
||||
await prisma.user.delete({
|
||||
where: { email: 'b@b.com' }
|
||||
})
|
||||
await prisma.user.delete({
|
||||
where: { email: testUserCredentials.email },
|
||||
})
|
||||
await prisma.user.delete({
|
||||
where: { email: "b@b.com" },
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /v1', () => {
|
||||
it('returns status code 200 and api version message', async () => {
|
||||
const res = await request(app)
|
||||
.get('/v1')
|
||||
describe("GET /v1", () => {
|
||||
it("returns status code 200 and api version message", async () => {
|
||||
const res = await request(app).get("/v1")
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /v1/session/register', () => {
|
||||
it('returns status code 200 and a success message', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/session/register')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(JSON.stringify({
|
||||
email: 'b@b.com',
|
||||
password: 'bbbbbbbbbbb'
|
||||
}))
|
||||
describe("POST /v1/session/register", () => {
|
||||
it("returns status code 200 and a success message", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/session/register")
|
||||
.set("Content-Type", "application/json")
|
||||
.send(
|
||||
JSON.stringify({
|
||||
email: "b@b.com",
|
||||
password: "bbbbbbbbbbb",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
|
||||
it('returns status code 400 and an error message', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/session/register')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(JSON.stringify(testUserCredentials))
|
||||
it("returns status code 400 and an error message", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/session/register")
|
||||
.set("Content-Type", "application/json")
|
||||
.send(JSON.stringify(testUserCredentials))
|
||||
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /v1/session/login', () => {
|
||||
it('returns status code 200 and set httpOnly jwt token cookie', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/session/login')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(JSON.stringify(testUserCredentials))
|
||||
describe("POST /v1/session/login", () => {
|
||||
it("returns status code 200 and set httpOnly jwt token cookie", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/session/login")
|
||||
.set("Content-Type", "application/json")
|
||||
.send(JSON.stringify(testUserCredentials))
|
||||
|
||||
const jwtRegEx = /^jwt=.*Path=\/.*HttpOnly.*Secure.*SameSite=Strict.*$/
|
||||
jwtCookie = res.get('Set-Cookie')
|
||||
?.filter(cookie => cookie.match(jwtRegEx))[0]
|
||||
const jwtRegEx = /^jwt=.*Path=\/.*HttpOnly.*Secure.*SameSite=Strict.*$/
|
||||
jwtCookie = res.get("Set-Cookie")?.filter((cookie) => cookie.match(jwtRegEx))[0]
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(jwtCookie).not.toBe(undefined)
|
||||
})
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(jwtCookie).not.toBe(undefined)
|
||||
})
|
||||
|
||||
it('returns status code 400 and credentials error message', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/session/login')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(JSON.stringify({
|
||||
email: 'rip bozo',
|
||||
password: 'rip bozo'
|
||||
}))
|
||||
it("returns status code 400 and credentials error message", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/session/login")
|
||||
.set("Content-Type", "application/json")
|
||||
.send(
|
||||
JSON.stringify({
|
||||
email: "rip bozo",
|
||||
password: "rip bozo",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /v1/category', () => {
|
||||
it('returns status code 200 and success message', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/category')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
name: 'VueJS Composition API'
|
||||
}))
|
||||
describe("POST /v1/category", () => {
|
||||
it("returns status code 200 and success message", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/category")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
name: "VueJS Composition API",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
|
||||
it('returns status code 400 and error message', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/category')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
pouet: 'VueJS Composition API'
|
||||
}))
|
||||
it("returns status code 400 and error message", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/category")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
pouet: "VueJS Composition API",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /v1/category/', () => {
|
||||
it('returns status code 200 and the categories', async () => {
|
||||
const res = await request(app)
|
||||
.get('/v1/category')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
describe("GET /v1/category/", () => {
|
||||
it("returns status code 200 and the categories", async () => {
|
||||
const res = await request(app)
|
||||
.get("/v1/category")
|
||||
.set("Content-Type", "application/json")
|
||||
.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.body.categories[0].name).toEqual('VueJS Composition API')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.categories[0].name).toEqual("VueJS Composition API")
|
||||
})
|
||||
|
||||
it('returns status code 200 and no categories', async () => {
|
||||
const res = await request(app)
|
||||
.get('/v1/category?skip=5&take=5')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
it("returns status code 200 and no categories", async () => {
|
||||
const res = await request(app)
|
||||
.get("/v1/category?skip=5&take=5")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
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.status).toEqual(200)
|
||||
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")
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /v1/category/:id', () => {
|
||||
it('returns status code 200 and the categories', async () => {
|
||||
const res = await request(app)
|
||||
.get(`/v1/category/${category_id}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
describe("GET /v1/category/:id", () => {
|
||||
it("returns status code 200 and the categories", async () => {
|
||||
const res = await request(app)
|
||||
.get(`/v1/category/${category_id}`)
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.category.name).toEqual('VueJS Composition API')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.category.name).toEqual("VueJS Composition API")
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /v1/category/:id', () => {
|
||||
it('returns status code 200 and a success message', async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/category/${category_id}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
name: 'VueJS v3 Composition API'
|
||||
}))
|
||||
describe("PUT /v1/category/:id", () => {
|
||||
it("returns status code 200 and a success message", async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/category/${category_id}`)
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
name: "VueJS v3 Composition API",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
|
||||
it('returns status code 400 and an error message', async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/category/${23591}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
pouet: 'salut'
|
||||
}))
|
||||
it("returns status code 400 and an error message", async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/category/${23591}`)
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
pouet: "salut",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(400)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /v1/snippet', () => {
|
||||
it('returns status code 200 and the snippets', async () => {
|
||||
const res = await request(app)
|
||||
.post('/v1/snippet')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
title: 'Vue3 CompAPI TS script-template-style',
|
||||
code: `<script setup lang="ts">
|
||||
describe("POST /v1/snippet", () => {
|
||||
it("returns status code 200 and the snippets", async () => {
|
||||
const res = await request(app)
|
||||
.post("/v1/snippet")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
title: "Vue3 CompAPI TS script-template-style",
|
||||
code: `<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -208,124 +219,130 @@ describe('POST /v1/snippet', () => {
|
||||
|
||||
<style scoped>
|
||||
</style>`,
|
||||
language: 'vue',
|
||||
tags: ['template', 'vuejs'],
|
||||
category_id
|
||||
}))
|
||||
language: "vue",
|
||||
tags: ["template", "vuejs"],
|
||||
category_id,
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /v1/snippet/', () => {
|
||||
it('returns status code 200 and all snippets', async () => {
|
||||
const res = await request(app)
|
||||
.get('/v1/snippet')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
describe("GET /v1/snippet/", () => {
|
||||
it("returns status code 200 and all snippets", async () => {
|
||||
const res = await request(app)
|
||||
.get("/v1/snippet")
|
||||
.set("Content-Type", "application/json")
|
||||
.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.body.snippets[0].title).toEqual('Vue3 CompAPI TS script-template-style')
|
||||
expect(res.body.snippets[0].category_id).toEqual(category_id)
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body.snippets[0].title).toEqual("Vue3 CompAPI TS script-template-style")
|
||||
expect(res.body.snippets[0].category_id).toEqual(category_id)
|
||||
})
|
||||
|
||||
it('returns status code 200 and no snippets', async () => {
|
||||
const res = await request(app)
|
||||
.get('/v1/snippet?skip=5&take=5')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
it("returns status code 200 and no snippets", async () => {
|
||||
const res = await request(app)
|
||||
.get("/v1/snippet?skip=5&take=5")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
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.status).toEqual(200)
|
||||
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")
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /v1/snippet/:id', () => {
|
||||
it('returns status code 200 and success message', async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/snippet/${snippet_id}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
code: '<p>en fait non à vuejs</p>',
|
||||
language: 'html',
|
||||
tags: ['pouet', 'pouet', 'pouet']
|
||||
}))
|
||||
describe("PUT /v1/snippet/:id", () => {
|
||||
it("returns status code 200 and success message", async () => {
|
||||
const res = await request(app)
|
||||
.put(`/v1/snippet/${snippet_id}`)
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
code: "<p>en fait non à vuejs</p>",
|
||||
language: "html",
|
||||
tags: ["pouet", "pouet", "pouet"],
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
})
|
||||
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)
|
||||
.delete(`/v1/snippet/${snippet_id}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
describe("DELETE /v1/snippet/:id", () => {
|
||||
it("returns status code 200 and success message", async () => {
|
||||
const res = await request(app)
|
||||
.delete(`/v1/snippet/${snippet_id}`)
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
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')
|
||||
})
|
||||
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)
|
||||
.put('/v1/user')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
id: 1234,
|
||||
name: 'didier',
|
||||
pouet: 'alo'
|
||||
}))
|
||||
describe("PUT /v1/user", () => {
|
||||
it("returns status code 200 and success message", async () => {
|
||||
const res = await request(app)
|
||||
.put("/v1/user")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
id: 1234,
|
||||
name: "didier",
|
||||
pouet: "alo",
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(200)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
|
||||
it('returns status code 400 and error message', async () => {
|
||||
const res = await request(app)
|
||||
.put('/v1/user')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Cookie', jwtCookie as string)
|
||||
.send(JSON.stringify({
|
||||
picture_path: 424242
|
||||
}))
|
||||
it("returns status code 400 and error message", async () => {
|
||||
const res = await request(app)
|
||||
.put("/v1/user")
|
||||
.set("Content-Type", "application/json")
|
||||
.set("Cookie", jwtCookie as string)
|
||||
.send(
|
||||
JSON.stringify({
|
||||
picture_path: 424242,
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty('message')
|
||||
})
|
||||
expect(res.statusCode).toEqual(400)
|
||||
expect(res.body).toHaveProperty("message")
|
||||
})
|
||||
})
|
||||
|
||||
+47
-47
@@ -1,73 +1,73 @@
|
||||
import express, { RequestHandler } from 'express'
|
||||
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 express, { RequestHandler } from "express"
|
||||
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.js'
|
||||
import userRouter from "./routers/user.js"
|
||||
import sessionRouter from "./routers/session.js"
|
||||
import categoryRouter from "./routers/category.js"
|
||||
import snippetRouter from "./routers/snippet.js"
|
||||
|
||||
const authMiddleware: RequestHandler = (req, res, next) => {
|
||||
const token = req.cookies.jwt
|
||||
if (token == undefined) {
|
||||
res.status(400).send({ message: 'Cannot find jwt auth cookie.' })
|
||||
return;
|
||||
}
|
||||
const token = req.cookies.jwt
|
||||
if (token == undefined) {
|
||||
res.status(400).send({ message: "Cannot find jwt auth cookie." })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
jwt.verify(token, getJwtSecret())
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: 'Incorrect JWT.', error })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
jwt.verify(token, getJwtSecret())
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: "Incorrect JWT.", error })
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
next()
|
||||
}
|
||||
|
||||
const appRouterGet: RequestHandler = (req, res) => {
|
||||
res.send({ code: 200, message: "SnippetsManager v1.0" })
|
||||
res.send({ code: 200, message: "SnippetsManager v1.0" })
|
||||
}
|
||||
|
||||
function initEnvVariables(): void {
|
||||
dotenv.config()
|
||||
const varsToCheck = ['API_PORT', 'FRONT_ORIGIN']
|
||||
dotenv.config()
|
||||
const varsToCheck = ["API_PORT", "FRONT_ORIGIN"]
|
||||
|
||||
varsToCheck.forEach(varName => {
|
||||
const variable = process.env[varName]
|
||||
varsToCheck.forEach((varName) => {
|
||||
const variable = process.env[varName]
|
||||
|
||||
if (variable == undefined || variable === '') {
|
||||
console.error(`Missing '${varName}' in .env`)
|
||||
process.exit()
|
||||
}
|
||||
})
|
||||
if (variable == undefined || variable === "") {
|
||||
console.error(`Missing '${varName}' in .env`)
|
||||
process.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function initServer(): express.Express {
|
||||
initEnvVariables()
|
||||
initEnvVariables()
|
||||
|
||||
const app = express()
|
||||
const appRouter = express.Router()
|
||||
const app = express()
|
||||
const appRouter = express.Router()
|
||||
|
||||
app.use(cors({ origin: process.env.FRONT_ORIGIN, credentials: true }))
|
||||
app.use(cookieParser())
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: false }))
|
||||
app.use(cors({ origin: process.env.FRONT_ORIGIN, credentials: true }))
|
||||
app.use(cookieParser())
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: false }))
|
||||
|
||||
appRouter.get('/', appRouterGet)
|
||||
appRouter.get("/", appRouterGet)
|
||||
|
||||
appRouter.use('/session/', sessionRouter)
|
||||
appRouter.use('/user/', authMiddleware, userRouter)
|
||||
appRouter.use('/category/', authMiddleware, categoryRouter)
|
||||
appRouter.use('/snippet/', authMiddleware, snippetRouter)
|
||||
appRouter.use("/session/", sessionRouter)
|
||||
appRouter.use("/user/", authMiddleware, userRouter)
|
||||
appRouter.use("/category/", authMiddleware, categoryRouter)
|
||||
appRouter.use("/snippet/", authMiddleware, snippetRouter)
|
||||
|
||||
app.use('/v1/', appRouter)
|
||||
app.use("/v1/", appRouter)
|
||||
|
||||
return app
|
||||
return app
|
||||
}
|
||||
|
||||
export function startServer(app: express.Express): void {
|
||||
app.listen(process.env.API_PORT, () => console.log(`Listening on port ${process.env.API_PORT}`))
|
||||
app.listen(process.env.API_PORT, () => console.log(`Listening on port ${process.env.API_PORT}`))
|
||||
}
|
||||
+94
-88
@@ -7,123 +7,129 @@ import { userIdMiddleware } from "./user.js"
|
||||
const categoryRouter = express.Router()
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
const categoryPostParser = z.object({
|
||||
name: z.string().max(50)
|
||||
}).required()
|
||||
const categoryPostParser = z
|
||||
.object({
|
||||
name: z.string().max(50),
|
||||
})
|
||||
.required()
|
||||
|
||||
const categoryUpdateParser = z.object({
|
||||
name: z.string().max(50)
|
||||
}).required()
|
||||
const categoryUpdateParser = z
|
||||
.object({
|
||||
name: z.string().max(50),
|
||||
})
|
||||
.required()
|
||||
|
||||
const paramsIdParser = z.object({
|
||||
id: z.coerce.number(),
|
||||
}).required()
|
||||
const paramsIdParser = z
|
||||
.object({
|
||||
id: z.coerce.number(),
|
||||
})
|
||||
.required()
|
||||
|
||||
async function findCategories(userId: number, pagination: Pagination): Promise<Category[]> {
|
||||
return await prisma.category.findMany({
|
||||
where: { user_id: userId },
|
||||
...pagination
|
||||
})
|
||||
return await prisma.category.findMany({
|
||||
where: { user_id: userId },
|
||||
...pagination,
|
||||
})
|
||||
}
|
||||
|
||||
const categoryGet: RequestHandler = async (req, res) => {
|
||||
let categories: Category[] | null = null
|
||||
const pagination = queryPaginationParser.parse(req.query)
|
||||
let categories: Category[] | null = null
|
||||
const pagination = queryPaginationParser.parse(req.query)
|
||||
|
||||
try {
|
||||
categories = await findCategories(req.body.userId, pagination)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
categories = await findCategories(req.body.userId, pagination)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({
|
||||
categories,
|
||||
pagination,
|
||||
links: getPaginationLinks(pagination, 'category'),
|
||||
total: categories.length
|
||||
})
|
||||
res.json({
|
||||
categories,
|
||||
pagination,
|
||||
links: getPaginationLinks(pagination, "category"),
|
||||
total: categories.length,
|
||||
})
|
||||
}
|
||||
|
||||
const categoryGetUnique: RequestHandler = async (req, res) => {
|
||||
let category: Category | null = null
|
||||
let category: Category | null = null
|
||||
|
||||
try {
|
||||
category = await prisma.category.findFirst({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
category = await prisma.category.findFirst({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ category })
|
||||
res.json({ category })
|
||||
}
|
||||
|
||||
const categoryPost: RequestHandler = async (req, res) => {
|
||||
try {
|
||||
const newCategory = categoryPostParser.parse(req.body)
|
||||
await prisma.category.create({
|
||||
data: {
|
||||
name: newCategory.name,
|
||||
user_id: req.body.userId
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newCategory = categoryPostParser.parse(req.body)
|
||||
await prisma.category.create({
|
||||
data: {
|
||||
name: newCategory.name,
|
||||
user_id: req.body.userId,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: 'Category successfully added.' })
|
||||
res.json({ message: "Category successfully added." })
|
||||
}
|
||||
|
||||
const categoryUpdate: RequestHandler = async (req, res) => {
|
||||
let updated: Prisma.BatchPayload
|
||||
let updated: Prisma.BatchPayload
|
||||
|
||||
try {
|
||||
const categoryToUpdate = categoryUpdateParser.parse(req.body)
|
||||
updated = await prisma.category.updateMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId
|
||||
},
|
||||
data: {
|
||||
name: categoryToUpdate.name
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const categoryToUpdate = categoryUpdateParser.parse(req.body)
|
||||
updated = await prisma.category.updateMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId,
|
||||
},
|
||||
data: {
|
||||
name: categoryToUpdate.name,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: `${updated.count} category / categories successfully updated.` })
|
||||
res.json({ message: `${updated.count} category / categories successfully updated.` })
|
||||
}
|
||||
|
||||
const categoryDelete: RequestHandler = async (req, res) => {
|
||||
let deleted: Prisma.BatchPayload
|
||||
let deleted: Prisma.BatchPayload
|
||||
|
||||
try {
|
||||
deleted = await prisma.category.deleteMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
deleted = await prisma.category.deleteMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
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('/:id', userIdMiddleware, categoryGetUnique)
|
||||
categoryRouter.post('/', userIdMiddleware, categoryPost)
|
||||
categoryRouter.put('/:id', userIdMiddleware, categoryUpdate)
|
||||
categoryRouter.delete('/:id', userIdMiddleware, categoryDelete)
|
||||
categoryRouter.get("/", userIdMiddleware, categoryGet)
|
||||
categoryRouter.get("/:id", userIdMiddleware, categoryGetUnique)
|
||||
categoryRouter.post("/", userIdMiddleware, categoryPost)
|
||||
categoryRouter.put("/:id", userIdMiddleware, categoryUpdate)
|
||||
categoryRouter.delete("/:id", userIdMiddleware, categoryDelete)
|
||||
|
||||
export default categoryRouter
|
||||
|
||||
+56
-56
@@ -3,98 +3,98 @@ import express from "express"
|
||||
import { RequestHandler } from "express-serve-static-core"
|
||||
import { z } from "zod"
|
||||
import { getJwtSecret } from "../utils/jwt.js"
|
||||
import bcrypt from 'bcrypt'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import bcrypt from "bcrypt"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
interface LoginData {
|
||||
email: string,
|
||||
password: string
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
const sessionRouter = express.Router()
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
const LoginValidator = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(4).max(20)
|
||||
email: z.string().email(),
|
||||
password: z.string().min(4).max(20),
|
||||
})
|
||||
|
||||
async function isUserValid(user: User | null, loginData: any): Promise<boolean> {
|
||||
if (user == undefined) return false
|
||||
return await bcrypt.compare(loginData.password, user.password)
|
||||
if (user == undefined) return false
|
||||
return await bcrypt.compare(loginData.password, user.password)
|
||||
}
|
||||
|
||||
async function isUserEmailAlreadyUsed(email: string): Promise<boolean> {
|
||||
const user = await prisma.user.findFirst({ where: { email } })
|
||||
return user != undefined
|
||||
const user = await prisma.user.findFirst({ where: { email } })
|
||||
return user != undefined
|
||||
}
|
||||
|
||||
async function createUser(email: string, password: string) {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: email,
|
||||
password: password,
|
||||
name: '',
|
||||
picture_path: '',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
},
|
||||
})
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
email: email,
|
||||
password: password,
|
||||
name: "",
|
||||
picture_path: "",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function getUserByEmail(email: string): Promise<User | null> {
|
||||
return await prisma.user.findFirst({ where: { email } })
|
||||
return await prisma.user.findFirst({ where: { email } })
|
||||
}
|
||||
|
||||
function parseLoginData(data: any): LoginData | undefined {
|
||||
try {
|
||||
return LoginValidator.parse(data)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
return LoginValidator.parse(data)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const userRouterPostLogin: RequestHandler = async (req, res) => {
|
||||
const loginData = parseLoginData(req.body)
|
||||
if (loginData === undefined) {
|
||||
res.status(400).json({ message: 'Incorrect credentials format.' })
|
||||
return;
|
||||
}
|
||||
const loginData = parseLoginData(req.body)
|
||||
if (loginData === undefined) {
|
||||
res.status(400).json({ message: "Incorrect credentials format." })
|
||||
return
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(loginData.email)
|
||||
if (!await isUserValid(user, loginData)) {
|
||||
res.status(400).json({ message: 'Incorrect credentials.' })
|
||||
return;
|
||||
}
|
||||
const user = await getUserByEmail(loginData.email)
|
||||
if (!(await isUserValid(user, loginData))) {
|
||||
res.status(400).json({ message: "Incorrect credentials." })
|
||||
return
|
||||
}
|
||||
|
||||
const jwtToken = jwt.sign({ userId: user?.id }, getJwtSecret(), { expiresIn: "1h" })
|
||||
res.cookie('jwt', jwtToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict'
|
||||
}).json({ message: 'Logged in! httpOnly cookie set.' })
|
||||
const jwtToken = jwt.sign({ userId: user?.id }, getJwtSecret(), { expiresIn: "1h" })
|
||||
res.cookie("jwt", jwtToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
}).json({ message: "Logged in! httpOnly cookie set." })
|
||||
}
|
||||
|
||||
export const userRouterPostRegister: RequestHandler = async (req, res) => {
|
||||
const loginData = parseLoginData(req.body)
|
||||
if (loginData == undefined) {
|
||||
res.status(400).json({ message: 'Incorrect credentials format.' })
|
||||
return;
|
||||
}
|
||||
const loginData = parseLoginData(req.body)
|
||||
if (loginData == undefined) {
|
||||
res.status(400).json({ message: "Incorrect credentials format." })
|
||||
return
|
||||
}
|
||||
|
||||
if (await isUserEmailAlreadyUsed(loginData.email)) {
|
||||
res.status(400).json({ message: 'Email already linked to an account.' })
|
||||
return;
|
||||
}
|
||||
if (await isUserEmailAlreadyUsed(loginData.email)) {
|
||||
res.status(400).json({ message: "Email already linked to an account." })
|
||||
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('/register', userRouterPostRegister)
|
||||
sessionRouter.post("/login", userRouterPostLogin)
|
||||
sessionRouter.post("/register", userRouterPostRegister)
|
||||
|
||||
export default sessionRouter
|
||||
+174
-161
@@ -7,213 +7,226 @@ import { userIdMiddleware } from "./user.js"
|
||||
const snippetRouter = express.Router()
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
const snippetPostParser = z.object({
|
||||
code: z.string(),
|
||||
title: z.string().max(50),
|
||||
language: z.string().max(32),
|
||||
tags: z.array(z.string()),
|
||||
category_id: z.number()
|
||||
}).required()
|
||||
const snippetPostParser = z
|
||||
.object({
|
||||
code: z.string(),
|
||||
title: z.string().max(50),
|
||||
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(),
|
||||
tags: z.array(z.string()).optional()
|
||||
code: z.string().optional(),
|
||||
title: z.string().max(50).optional(),
|
||||
language: z.string().max(32).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
const paramsIdParser = z.object({
|
||||
id: z.coerce.number(),
|
||||
}).required()
|
||||
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
|
||||
})
|
||||
snippets.forEach((snippet: any) => {
|
||||
snippet.tags = snippet.Snippet_tag.map((x: any) => ({ ...x.tag }))
|
||||
snippet.Snippet_tag = undefined
|
||||
})
|
||||
}
|
||||
|
||||
async function findSnippets(userId: number, pagination: Pagination): Promise<Snippet[]> {
|
||||
return await prisma.snippet.findMany({
|
||||
where: { user_id: userId },
|
||||
include: {
|
||||
Snippet_tag: {
|
||||
select: { tag: { select: { id: true, name: true } } },
|
||||
}
|
||||
},
|
||||
...pagination
|
||||
})
|
||||
return await prisma.snippet.findMany({
|
||||
where: { user_id: userId },
|
||||
include: {
|
||||
Snippet_tag: {
|
||||
select: { tag: { select: { id: true, name: true } } },
|
||||
},
|
||||
},
|
||||
...pagination,
|
||||
})
|
||||
}
|
||||
|
||||
const snippetGet: RequestHandler = async (req, res) => {
|
||||
let snippets: Snippet[] | null = null
|
||||
const pagination = queryPaginationParser.parse(req.query)
|
||||
let snippets: Snippet[] | null = null
|
||||
const pagination = queryPaginationParser.parse(req.query)
|
||||
|
||||
try {
|
||||
snippets = await findSnippets(req.body.userId, pagination)
|
||||
shortenSnippetsTagDepth(snippets)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
snippets = await findSnippets(req.body.userId, pagination)
|
||||
shortenSnippetsTagDepth(snippets)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({
|
||||
snippets,
|
||||
pagination,
|
||||
links: getPaginationLinks(pagination, 'snippet'),
|
||||
total: snippets.length
|
||||
})
|
||||
res.json({
|
||||
snippets,
|
||||
pagination,
|
||||
links: getPaginationLinks(pagination, "snippet"),
|
||||
total: snippets.length,
|
||||
})
|
||||
}
|
||||
|
||||
const snippetGetUnique: RequestHandler = async (req, res) => {
|
||||
let snippet: Snippet | null = null
|
||||
let snippet: Snippet | null = null
|
||||
|
||||
try {
|
||||
snippet = await prisma.snippet.findFirst({
|
||||
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;
|
||||
}
|
||||
try {
|
||||
snippet = await prisma.snippet.findFirst({
|
||||
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
|
||||
}
|
||||
|
||||
res.json({ snippet })
|
||||
res.json({ snippet })
|
||||
}
|
||||
|
||||
async function deleteSnippetTags(userId: number, snippetId: number): Promise<void> {
|
||||
await prisma.snippet_tag.deleteMany({
|
||||
where: {
|
||||
tag: { user_id: userId },
|
||||
snippet_id: snippetId
|
||||
}
|
||||
})
|
||||
await prisma.snippet_tag.deleteMany({
|
||||
where: {
|
||||
tag: { user_id: userId },
|
||||
snippet_id: snippetId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function createSnippetTag(snippetId: number, userId: number, tagName: string): Promise<void> {
|
||||
await prisma.snippet_tag.create({
|
||||
data: {
|
||||
snippet: {
|
||||
connect: { id: snippetId }
|
||||
},
|
||||
tag: {
|
||||
create: {
|
||||
name: tagName,
|
||||
user_id: userId
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
await prisma.snippet_tag.create({
|
||||
data: {
|
||||
snippet: {
|
||||
connect: { id: snippetId },
|
||||
},
|
||||
tag: {
|
||||
create: {
|
||||
name: tagName,
|
||||
user_id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function updateSnippet(snippetId: number, userId: number, languageId: number | undefined, snippetData: any): Promise<Prisma.BatchPayload> {
|
||||
return await prisma.snippet.updateMany({
|
||||
where: {
|
||||
id: snippetId,
|
||||
user_id: userId
|
||||
},
|
||||
data: {
|
||||
title: snippetData.title,
|
||||
code: snippetData.code,
|
||||
user_id: userId,
|
||||
language_id: languageId,
|
||||
}
|
||||
})
|
||||
async function updateSnippet(
|
||||
snippetId: number,
|
||||
userId: number,
|
||||
languageId: number | undefined,
|
||||
snippetData: any
|
||||
): Promise<Prisma.BatchPayload> {
|
||||
return await prisma.snippet.updateMany({
|
||||
where: {
|
||||
id: snippetId,
|
||||
user_id: userId,
|
||||
},
|
||||
data: {
|
||||
title: snippetData.title,
|
||||
code: snippetData.code,
|
||||
user_id: userId,
|
||||
language_id: languageId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function updateTagsFromSnippet(snippetId: number, userId: number, snippetData: any): Promise<void> {
|
||||
if (snippetData.tags == undefined) return;
|
||||
async function updateTagsFromSnippet(
|
||||
snippetId: number,
|
||||
userId: number,
|
||||
snippetData: any
|
||||
): Promise<void> {
|
||||
if (snippetData.tags == undefined) return
|
||||
|
||||
await deleteSnippetTags(userId, snippetId)
|
||||
await deleteSnippetTags(userId, snippetId)
|
||||
|
||||
for (const tag of snippetData.tags) {
|
||||
await createSnippetTag(snippetId, userId, tag)
|
||||
}
|
||||
for (const tag of snippetData.tags) {
|
||||
await createSnippetTag(snippetId, userId, tag)
|
||||
}
|
||||
}
|
||||
|
||||
const snippetPost: RequestHandler = async (req, res) => {
|
||||
try {
|
||||
const newSnippet = snippetPostParser.parse(req.body)
|
||||
const snippet = await prisma.snippet.create({
|
||||
data: {
|
||||
title: newSnippet.title,
|
||||
code: newSnippet.code,
|
||||
user: {
|
||||
connect: { id: req.body.userId }
|
||||
},
|
||||
language: {
|
||||
connectOrCreate: {
|
||||
where: { name: newSnippet.language },
|
||||
create: { name: newSnippet.language }
|
||||
}
|
||||
},
|
||||
category: {
|
||||
connect: { id: newSnippet.category_id }
|
||||
}
|
||||
}
|
||||
})
|
||||
for (const tag of newSnippet.tags) {
|
||||
await createSnippetTag(snippet.id, req.body.userId, tag)
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newSnippet = snippetPostParser.parse(req.body)
|
||||
const snippet = await prisma.snippet.create({
|
||||
data: {
|
||||
title: newSnippet.title,
|
||||
code: newSnippet.code,
|
||||
user: {
|
||||
connect: { id: req.body.userId },
|
||||
},
|
||||
language: {
|
||||
connectOrCreate: {
|
||||
where: { name: newSnippet.language },
|
||||
create: { name: newSnippet.language },
|
||||
},
|
||||
},
|
||||
category: {
|
||||
connect: { id: newSnippet.category_id },
|
||||
},
|
||||
},
|
||||
})
|
||||
for (const tag of newSnippet.tags) {
|
||||
await createSnippetTag(snippet.id, req.body.userId, tag)
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: 'Snippet successfully added.' })
|
||||
res.json({ message: "Snippet successfully added." })
|
||||
}
|
||||
|
||||
const snippetUpdate: RequestHandler = async (req, res) => {
|
||||
let updated: Prisma.BatchPayload
|
||||
let updated: Prisma.BatchPayload
|
||||
|
||||
try {
|
||||
const snippetData = snippetUpdateParser.parse(req.body)
|
||||
const languageId = await prisma.language.findFirst({
|
||||
where: { name: snippetData.language }
|
||||
})
|
||||
const snippetId = paramsIdParser.parse(req.params).id
|
||||
updated = await updateSnippet(snippetId, req.body.userId, languageId?.id, snippetData)
|
||||
await updateTagsFromSnippet(snippetId, req.body.userId, snippetData)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const snippetData = snippetUpdateParser.parse(req.body)
|
||||
const languageId = await prisma.language.findFirst({
|
||||
where: { name: snippetData.language },
|
||||
})
|
||||
const snippetId = paramsIdParser.parse(req.params).id
|
||||
updated = await updateSnippet(snippetId, req.body.userId, languageId?.id, snippetData)
|
||||
await updateTagsFromSnippet(snippetId, req.body.userId, snippetData)
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: `${updated.count} snippet / categories successfully updated.` })
|
||||
res.json({ message: `${updated.count} snippet / categories successfully updated.` })
|
||||
}
|
||||
|
||||
const snippetDelete: RequestHandler = async (req, res) => {
|
||||
let deleted: Prisma.BatchPayload
|
||||
let deleted: Prisma.BatchPayload
|
||||
|
||||
try {
|
||||
deleted = await prisma.category.deleteMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
deleted = await prisma.category.deleteMany({
|
||||
where: {
|
||||
id: paramsIdParser.parse(req.params).id,
|
||||
user_id: req.body.userId,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
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('/:id', userIdMiddleware, snippetGetUnique)
|
||||
snippetRouter.post('/', userIdMiddleware, snippetPost)
|
||||
snippetRouter.put('/:id', userIdMiddleware, snippetUpdate)
|
||||
snippetRouter.delete('/:id', userIdMiddleware, snippetDelete)
|
||||
snippetRouter.get("/", userIdMiddleware, snippetGet)
|
||||
snippetRouter.get("/:id", userIdMiddleware, snippetGetUnique)
|
||||
snippetRouter.post("/", userIdMiddleware, snippetPost)
|
||||
snippetRouter.put("/:id", userIdMiddleware, snippetUpdate)
|
||||
snippetRouter.delete("/:id", userIdMiddleware, snippetDelete)
|
||||
|
||||
export default snippetRouter
|
||||
|
||||
+35
-35
@@ -1,54 +1,54 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import express, { RequestHandler } from 'express'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { z } from 'zod'
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
import express, { RequestHandler } from "express"
|
||||
import jwt from "jsonwebtoken"
|
||||
import { z } from "zod"
|
||||
|
||||
const userRouter = express.Router()
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
const editableUserData = z.object({
|
||||
name: z.string().optional(),
|
||||
picture_path: z.string().optional()
|
||||
name: z.string().optional(),
|
||||
picture_path: z.string().optional(),
|
||||
})
|
||||
|
||||
export function parseJwtUserId(jwtoken: string): number | undefined {
|
||||
const payload = jwt.decode(jwtoken)
|
||||
if (payload == undefined) {
|
||||
return undefined
|
||||
} else if (typeof payload == 'string') {
|
||||
return undefined
|
||||
}
|
||||
return payload.userId
|
||||
const payload = jwt.decode(jwtoken)
|
||||
if (payload == undefined) {
|
||||
return undefined
|
||||
} else if (typeof payload == "string") {
|
||||
return undefined
|
||||
}
|
||||
return payload.userId
|
||||
}
|
||||
|
||||
export const userIdMiddleware: RequestHandler = (req, res, next) => {
|
||||
const userId = parseJwtUserId(req.cookies.jwt)
|
||||
if (userId === undefined) {
|
||||
res.status(400).json({ message: 'Incorrect JWT payload.' })
|
||||
return;
|
||||
}
|
||||
req.body.userId = userId
|
||||
next()
|
||||
const userId = parseJwtUserId(req.cookies.jwt)
|
||||
if (userId === undefined) {
|
||||
res.status(400).json({ message: "Incorrect JWT payload." })
|
||||
return
|
||||
}
|
||||
req.body.userId = userId
|
||||
next()
|
||||
}
|
||||
|
||||
const userRouterPut: RequestHandler = async (req, res) => {
|
||||
const userId = parseJwtUserId(req.cookies.jwt)
|
||||
if (userId === undefined) {
|
||||
res.status(400).json({ message: 'Incorrect JWT payload.' })
|
||||
return;
|
||||
}
|
||||
const userId = parseJwtUserId(req.cookies.jwt)
|
||||
if (userId === undefined) {
|
||||
res.status(400).json({ message: "Incorrect JWT payload." })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: editableUserData.parse(req.body)
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: (error.issues ?? error) })
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: editableUserData.parse(req.body),
|
||||
})
|
||||
} catch (error: any) {
|
||||
res.status(400).json({ message: error.issues ?? error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: 'User successfully updated.' })
|
||||
res.json({ message: "User successfully updated." })
|
||||
}
|
||||
|
||||
userRouter.put("/", userRouterPut)
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
export function getJwtSecret(): string {
|
||||
if (process.env["JWT_SECRET"] == undefined) {
|
||||
console.error("WARNING! JWT secret is not set.")
|
||||
process.exit()
|
||||
}
|
||||
return process.env["JWT_SECRET"]
|
||||
if (process.env["JWT_SECRET"] == undefined) {
|
||||
console.error("WARNING! JWT secret is not set.")
|
||||
process.exit()
|
||||
}
|
||||
return process.env["JWT_SECRET"]
|
||||
}
|
||||
+10
-8
@@ -1,18 +1,20 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export interface Pagination {
|
||||
skip: number,
|
||||
take: number
|
||||
skip: number
|
||||
take: number
|
||||
}
|
||||
|
||||
export const queryPaginationParser = z.object({
|
||||
skip: z.coerce.number().optional().default(0),
|
||||
take: z.coerce.number().optional().default(10)
|
||||
skip: z.coerce.number().optional().default(0),
|
||||
take: z.coerce.number().optional().default(10),
|
||||
})
|
||||
|
||||
export function getPaginationLinks(query: Pagination, routeName: string): object {
|
||||
return {
|
||||
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}`
|
||||
}
|
||||
return {
|
||||
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
|
||||
}`,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user