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",
|
"name": "snippetsmanager",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"prettier:fix": "prettier --write ./src",
|
||||||
"build:dev": "tsc -w",
|
"prettier:check": "prettier --check ./src",
|
||||||
"start": "concurrently npm:build \"wait-on dist/index.js && node dist/index.js\"",
|
"build": "tsc",
|
||||||
"dev": "concurrently npm:build:dev \"wait-on dist/index.js && nodemon dist/index.js\"",
|
"build:dev": "tsc -w",
|
||||||
"test": "jest"
|
"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\"",
|
||||||
"author": "",
|
"test": "jest"
|
||||||
"license": "ISC",
|
},
|
||||||
"dependencies": {
|
"author": "",
|
||||||
"@prisma/client": "^4.10.1",
|
"license": "ISC",
|
||||||
"bcrypt": "^5.1.0",
|
"dependencies": {
|
||||||
"concurrently": "^7.6.0",
|
"@prisma/client": "^4.10.1",
|
||||||
"cookie-parser": "^1.4.6",
|
"bcrypt": "^5.1.0",
|
||||||
"cors": "^2.8.5",
|
"concurrently": "^7.6.0",
|
||||||
"dotenv": "^16.0.3",
|
"cookie-parser": "^1.4.6",
|
||||||
"express": "^4.18.2",
|
"cors": "^2.8.5",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"dotenv": "^16.0.3",
|
||||||
"nodemon": "^2.0.20",
|
"express": "^4.18.2",
|
||||||
"wait-on": "^7.0.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"zod": "^3.20.2"
|
"nodemon": "^2.0.20",
|
||||||
},
|
"prisma": "^4.10.1",
|
||||||
"devDependencies": {
|
"wait-on": "^7.0.1",
|
||||||
"@babel/preset-typescript": "^7.18.6",
|
"zod": "^3.20.2"
|
||||||
"@types/bcrypt": "^5.0.0",
|
},
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.13",
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
"@types/express": "^4.17.16",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/jest": "^29.4.0",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/jsonwebtoken": "^9.0.1",
|
"@types/cors": "^2.8.13",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/express": "^4.17.16",
|
||||||
"install": "^0.13.0",
|
"@types/jest": "^29.4.0",
|
||||||
"jest": "^29.4.1",
|
"@types/jsonwebtoken": "^9.0.1",
|
||||||
"npm": "^9.4.0",
|
"@types/supertest": "^2.0.12",
|
||||||
"prisma": "^4.10.1",
|
"jest": "^29.4.1",
|
||||||
"supertest": "^6.3.3",
|
"npm": "^9.4.0",
|
||||||
"ts-jest": "^29.0.5",
|
"prettier": "^2.8.4",
|
||||||
"ts-node": "^10.9.1",
|
"supertest": "^6.3.3",
|
||||||
"typescript": "^4.9.4"
|
"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 { 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()
|
||||||
let jwtCookie: string | undefined
|
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
|
||||||
let snippet_id: number
|
let snippet_id: number
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
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({
|
||||||
code: `<script setup lang="ts">
|
title: "Vue3 CompAPI TS script-template-style",
|
||||||
|
code: `<script setup lang="ts">
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -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", () => {
|
||||||
|
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', () => {
|
expect(res.status).toEqual(200)
|
||||||
it('returns status code 200 and one snippet', async () => {
|
expect(res.body.snippet.tags.length).toEqual(3)
|
||||||
const res = await request(app)
|
expect(res.body.snippet.tags).not.toContain("vuejs")
|
||||||
.get(`/v1/snippet/${snippet_id}`)
|
expect(res.body.snippet.category_id).toEqual(category_id)
|
||||||
.set('Content-Type', 'application/json')
|
})
|
||||||
.set('Cookie', jwtCookie as string)
|
|
||||||
|
|
||||||
expect(res.status).toEqual(200)
|
|
||||||
expect(res.body.snippet.tags.length).toEqual(3)
|
|
||||||
expect(res.body.snippet.tags).not.toContain('vuejs')
|
|
||||||
expect(res.body.snippet.category_id).toEqual(category_id)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('DELETE /v1/snippet/:id', () => {
|
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(
|
||||||
id: 1234,
|
JSON.stringify({
|
||||||
name: 'didier',
|
id: 1234,
|
||||||
pouet: 'alo'
|
name: "didier",
|
||||||
}))
|
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")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+47
-47
@@ -1,73 +1,73 @@
|
|||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
const appRouterGet: RequestHandler = (req, res) => {
|
const appRouterGet: RequestHandler = (req, res) => {
|
||||||
res.send({ code: 200, message: "SnippetsManager v1.0" })
|
res.send({ code: 200, message: "SnippetsManager v1.0" })
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initServer(): express.Express {
|
export function initServer(): express.Express {
|
||||||
initEnvVariables()
|
initEnvVariables()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const appRouter = express.Router()
|
const appRouter = express.Router()
|
||||||
|
|
||||||
app.use(cors({ origin: process.env.FRONT_ORIGIN, credentials: true }))
|
app.use(cors({ origin: process.env.FRONT_ORIGIN, credentials: true }))
|
||||||
app.use(cookieParser())
|
app.use(cookieParser())
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startServer(app: express.Express): void {
|
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 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
|
||||||
id: z.coerce.number(),
|
.object({
|
||||||
}).required()
|
id: z.coerce.number(),
|
||||||
|
})
|
||||||
|
.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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryGet: RequestHandler = async (req, res) => {
|
const categoryGet: RequestHandler = async (req, res) => {
|
||||||
let categories: Category[] | null = null
|
let categories: Category[] | null = null
|
||||||
const pagination = queryPaginationParser.parse(req.query)
|
const pagination = queryPaginationParser.parse(req.query)
|
||||||
|
|
||||||
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryGetUnique: RequestHandler = async (req, res) => {
|
const categoryGetUnique: RequestHandler = async (req, res) => {
|
||||||
let category: Category | null = null
|
let category: Category | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryPost: RequestHandler = async (req, res) => {
|
const categoryPost: RequestHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const newCategory = categoryPostParser.parse(req.body)
|
const newCategory = categoryPostParser.parse(req.body)
|
||||||
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) => {
|
||||||
let updated: Prisma.BatchPayload
|
let updated: Prisma.BatchPayload
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const categoryToUpdate = categoryUpdateParser.parse(req.body)
|
const categoryToUpdate = categoryUpdateParser.parse(req.body)
|
||||||
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.` })
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryDelete: RequestHandler = async (req, res) => {
|
const categoryDelete: RequestHandler = async (req, res) => {
|
||||||
let deleted: Prisma.BatchPayload
|
let deleted: Prisma.BatchPayload
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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
|
||||||
|
|||||||
+56
-56
@@ -3,98 +3,98 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionRouter = express.Router()
|
const sessionRouter = express.Router()
|
||||||
const prisma = new PrismaClient()
|
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> {
|
||||||
if (user == undefined) return false
|
if (user == undefined) return false
|
||||||
return await bcrypt.compare(loginData.password, user.password)
|
return await bcrypt.compare(loginData.password, user.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isUserEmailAlreadyUsed(email: string): Promise<boolean> {
|
async function isUserEmailAlreadyUsed(email: string): Promise<boolean> {
|
||||||
const user = await prisma.user.findFirst({ where: { email } })
|
const user = await prisma.user.findFirst({ where: { email } })
|
||||||
return user != undefined
|
return user != undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUser(email: string, password: string) {
|
async function createUser(email: string, password: string) {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
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(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUserByEmail(email: string): Promise<User | null> {
|
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 {
|
function parseLoginData(data: any): LoginData | undefined {
|
||||||
try {
|
try {
|
||||||
return LoginValidator.parse(data)
|
return LoginValidator.parse(data)
|
||||||
} catch {
|
} catch {
|
||||||
return undefined
|
return 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
|
||||||
+174
-161
@@ -7,213 +7,226 @@ 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
|
||||||
code: z.string(),
|
.object({
|
||||||
title: z.string().max(50),
|
code: z.string(),
|
||||||
language: z.string().max(32),
|
title: z.string().max(50),
|
||||||
tags: z.array(z.string()),
|
language: z.string().max(32),
|
||||||
category_id: z.number()
|
tags: z.array(z.string()),
|
||||||
}).required()
|
category_id: z.number(),
|
||||||
|
})
|
||||||
|
.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
|
||||||
id: z.coerce.number(),
|
.object({
|
||||||
}).required()
|
id: z.coerce.number(),
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
|
||||||
function shortenSnippetsTagDepth(snippets: Snippet[]) {
|
function shortenSnippetsTagDepth(snippets: Snippet[]) {
|
||||||
snippets.forEach((snippet: any) => {
|
snippets.forEach((snippet: any) => {
|
||||||
snippet.tags = snippet.Snippet_tag.map((x: any) => ({ ...x.tag }))
|
snippet.tags = snippet.Snippet_tag.map((x: any) => ({ ...x.tag }))
|
||||||
snippet.Snippet_tag = undefined
|
snippet.Snippet_tag = undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findSnippets(userId: number, pagination: Pagination): Promise<Snippet[]> {
|
async function findSnippets(userId: number, pagination: Pagination): Promise<Snippet[]> {
|
||||||
return await prisma.snippet.findMany({
|
return await prisma.snippet.findMany({
|
||||||
where: { user_id: userId },
|
where: { user_id: userId },
|
||||||
include: {
|
include: {
|
||||||
Snippet_tag: {
|
Snippet_tag: {
|
||||||
select: { tag: { select: { id: true, name: true } } },
|
select: { tag: { select: { id: true, name: true } } },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
...pagination
|
...pagination,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const snippetGet: RequestHandler = async (req, res) => {
|
const snippetGet: RequestHandler = async (req, res) => {
|
||||||
let snippets: Snippet[] | null = null
|
let snippets: Snippet[] | null = null
|
||||||
const pagination = queryPaginationParser.parse(req.query)
|
const pagination = queryPaginationParser.parse(req.query)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const snippetGetUnique: RequestHandler = async (req, res) => {
|
const snippetGetUnique: RequestHandler = async (req, res) => {
|
||||||
let snippet: Snippet | null = null
|
let snippet: Snippet | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSnippetTags(userId: number, snippetId: number): Promise<void> {
|
async function deleteSnippetTags(userId: number, snippetId: number): Promise<void> {
|
||||||
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,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSnippetTag(snippetId: number, userId: number, tagName: string): Promise<void> {
|
async function createSnippetTag(snippetId: number, userId: number, tagName: string): Promise<void> {
|
||||||
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(
|
||||||
return await prisma.snippet.updateMany({
|
snippetId: number,
|
||||||
where: {
|
userId: number,
|
||||||
id: snippetId,
|
languageId: number | undefined,
|
||||||
user_id: userId
|
snippetData: any
|
||||||
},
|
): Promise<Prisma.BatchPayload> {
|
||||||
data: {
|
return await prisma.snippet.updateMany({
|
||||||
title: snippetData.title,
|
where: {
|
||||||
code: snippetData.code,
|
id: snippetId,
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
language_id: languageId,
|
},
|
||||||
}
|
data: {
|
||||||
})
|
title: snippetData.title,
|
||||||
|
code: snippetData.code,
|
||||||
|
user_id: userId,
|
||||||
|
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)
|
||||||
|
|
||||||
for (const tag of snippetData.tags) {
|
for (const tag of snippetData.tags) {
|
||||||
await createSnippetTag(snippetId, userId, tag)
|
await createSnippetTag(snippetId, userId, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const snippetPost: RequestHandler = async (req, res) => {
|
const snippetPost: RequestHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const newSnippet = snippetPostParser.parse(req.body)
|
const newSnippet = snippetPostParser.parse(req.body)
|
||||||
const snippet = await prisma.snippet.create({
|
const snippet = await prisma.snippet.create({
|
||||||
data: {
|
data: {
|
||||||
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) => {
|
||||||
let updated: Prisma.BatchPayload
|
let updated: Prisma.BatchPayload
|
||||||
|
|
||||||
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.` })
|
||||||
}
|
}
|
||||||
|
|
||||||
const snippetDelete: RequestHandler = async (req, res) => {
|
const snippetDelete: RequestHandler = async (req, res) => {
|
||||||
let deleted: Prisma.BatchPayload
|
let deleted: Prisma.BatchPayload
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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
|
||||||
|
|||||||
+35
-35
@@ -1,54 +1,54 @@
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
-5
@@ -1,7 +1,7 @@
|
|||||||
export function getJwtSecret(): string {
|
export function getJwtSecret(): string {
|
||||||
if (process.env["JWT_SECRET"] == undefined) {
|
if (process.env["JWT_SECRET"] == undefined) {
|
||||||
console.error("WARNING! JWT secret is not set.")
|
console.error("WARNING! JWT secret is not set.")
|
||||||
process.exit()
|
process.exit()
|
||||||
}
|
}
|
||||||
return process.env["JWT_SECRET"]
|
return process.env["JWT_SECRET"]
|
||||||
}
|
}
|
||||||
+10
-8
@@ -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
|
||||||
|
}`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user