Updated dependencies, added and ran prettier

This commit is contained in:
Florian Sylvain
2023-02-23 10:08:35 +01:00
parent 8c0549226c
commit 53795764dc
12 changed files with 10799 additions and 9383 deletions
+10
View File
@@ -0,0 +1,10 @@
mysql-data
node_modules
dist
coverage
.env
.vscode
.idea
+6
View File
@@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": false,
"printWidth": 100
}
+10046 -8686
View File
File diff suppressed because it is too large Load Diff
+47 -45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}`,
}
}