mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +00:00
feat: removed expressjs in favor of nestjs
This commit is contained in:
parent
05a53252a4
commit
7d3afa3dcd
114 changed files with 5738 additions and 12306 deletions
43
.gitignore
vendored
43
.gitignore
vendored
|
@ -1,11 +1,42 @@
|
|||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
old_port
|
||||
.git
|
||||
node_modules
|
||||
.env
|
||||
prisma/*.db
|
||||
.DS_Store
|
||||
prisma/migrations/dev
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
package_backup.json
|
||||
logs/
|
||||
docker.env
|
26
.swcrc
26
.swcrc
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": false,
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"target": "es2020",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"clients/*": ["clients/*"],
|
||||
"config/*": ["config/*"],
|
||||
"controllers/*": ["controllers/*"],
|
||||
"interfaces/*": ["interfaces/*"],
|
||||
"helpers/*": ["helpers/*"],
|
||||
"middlewares/*": ["middlewares/*"],
|
||||
"services/*": ["services/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["@types/", "interfaces/"],
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
},
|
||||
"minify": true
|
||||
}
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"biomejs.biome",
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
|
@ -8,5 +8,10 @@
|
|||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
}
|
||||
};
|
||||
|
|
8
nest-cli.json
Normal file
8
nest-cli.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
14344
package-lock.json
generated
14344
package-lock.json
generated
File diff suppressed because it is too large
Load diff
131
package.json
131
package.json
|
@ -1,85 +1,80 @@
|
|||
{
|
||||
"name": "social-media-app",
|
||||
"name": "project-knedita",
|
||||
"version": "0.0.1",
|
||||
"description": "A social media",
|
||||
"description": "A open-source social media",
|
||||
"author": "CookieDasora",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "swc src -d dist",
|
||||
"dev:start": "ts-node-dev -r tsconfig-paths/register --transpile-only --respawn src/server.ts",
|
||||
"build": "nest build",
|
||||
"dev:start": "nest start --watch",
|
||||
"dev:debug": "nest start --debug --watch",
|
||||
"docker": "docker compose --env-file docker.env up -d",
|
||||
"docker:build": "docker build -t api . && docker compose up -d",
|
||||
"docker:db": "docker compose -f docker-compose.db.yml up -d",
|
||||
"docker:seed": "docker exec -it api npm run prisma:seed",
|
||||
"lint": "eslint --ignore-path .eslintignore --ext .js,.ts .",
|
||||
"migrate:dev": "prisma migrate dev",
|
||||
"migrate:dev:create": "prisma migrate dev --create-only",
|
||||
"migrate:reset": "prisma migrate reset",
|
||||
"prepare": "husky install",
|
||||
"prisma:generate": "npx prisma generate",
|
||||
"prisma:seed": "prisma db seed",
|
||||
"prisma:studio": "npx prisma studio",
|
||||
"prod:start": "npx prisma migrate deploy && pm2-runtime start dist/server.js",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"ts-standard": {
|
||||
"project": "tsconfig.json",
|
||||
"ignore": [
|
||||
"prisma/*",
|
||||
"dist"
|
||||
]
|
||||
},
|
||||
"author": "CookieDasora",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.3",
|
||||
"@commitlint/cli": "^17.7.2",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.92",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.19",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/multer-s3": "^3.0.0",
|
||||
"@types/node": "^20.11.5",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/swagger-ui-express": "^4.1.4",
|
||||
"@types/validator": "^13.7.17",
|
||||
"husky": "^8.0.3",
|
||||
"nodemon": "^3.0.1",
|
||||
"pm2": "^5.3.1",
|
||||
"prisma": "^5.7.0",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"vitest": "^1.2.1"
|
||||
"prepare": "husky install",
|
||||
"prod": "node dist/main",
|
||||
"start": "nest start",
|
||||
"test": "jest",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.4.1",
|
||||
"aws-sdk": "^2.1536.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.2",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.1",
|
||||
"ioredis": "^5.3.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-gyp": "^10.0.1",
|
||||
"rate-limit-redis": "^4.0.0",
|
||||
"redis": "^4.6.7",
|
||||
"sharp": "^0.32.3",
|
||||
"socket.io": "^4.7.2",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"validator": "^13.9.0",
|
||||
"winston": "^3.11.0",
|
||||
"yaml": "^2.3.4"
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^7.2.0",
|
||||
"nestjs-zod": "^3.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.3",
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
12
src/@types/express.d.ts
vendored
12
src/@types/express.d.ts
vendored
|
@ -1,12 +0,0 @@
|
|||
import * as express from "express";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
namespace Multer {
|
||||
interface File {
|
||||
location: string;
|
||||
key: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { expect, describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("POST /post/create", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteUser(user.username as string); // TODO: need to take a look at this
|
||||
});
|
||||
|
||||
it("should respond with 200 status code if the user send the token and the content", async () => {
|
||||
const response = await request(app)
|
||||
.post("/post/create")
|
||||
.send({
|
||||
content: "Hello world",
|
||||
})
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
content: expect.any(String),
|
||||
authorId: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should respond with 400 status code if the user send no token", async () => {
|
||||
const response = await request(app).post("/post/create").expect(401);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
});
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("DELETE /post/delete", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteUser(user.username as string); // TODO: here too
|
||||
});
|
||||
|
||||
it("should delete the post successfully", async () => {
|
||||
const response = await request(app)
|
||||
.post("/post/create")
|
||||
.send({
|
||||
content: "lorem ipsum",
|
||||
})
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app)
|
||||
.post("/post/delete")
|
||||
.send({
|
||||
postId: response.body.id,
|
||||
})
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { expect, describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let postId: string;
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("POST /post/info", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
|
||||
const token = user.token ?? "";
|
||||
|
||||
const post = await request(app)
|
||||
.post("/post/create")
|
||||
.send({
|
||||
content: "Hello world",
|
||||
})
|
||||
.set("Authorization", `Bearer ${token}`)
|
||||
.expect(200);
|
||||
|
||||
postId = post.body.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteUser(user.username as string); // TODO: here too btw
|
||||
});
|
||||
|
||||
it("should respond with 200 status code and return some info about the post", async () => {
|
||||
const response = await request(app)
|
||||
.get(`/post/info?id=${postId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
content: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
author: expect.any(Object),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should respond with 400 status code if the post does not exists", async () => {
|
||||
const response = await request(app).get("/post/info?id=abc").expect(400);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
});
|
||||
});
|
|
@ -1,56 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { expect, describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("PUT /post/update", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteUser(user.username as string); // TODO: well, here too
|
||||
});
|
||||
|
||||
it("should create a new post and update the content of it", async () => {
|
||||
const post = await request(app)
|
||||
.post("/post/create")
|
||||
.send({
|
||||
content: "Lorem",
|
||||
})
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
|
||||
expect(post.body).toHaveProperty("id");
|
||||
|
||||
const fieldsToUpdate = {
|
||||
postId: post.body.id,
|
||||
content: "Lorem ipsum",
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.put("/post/update")
|
||||
.send(fieldsToUpdate)
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
|
||||
// Post content should be Lorem Ipsum
|
||||
if (post.body.content === response.body.content) {
|
||||
throw new Error("Post didn't update");
|
||||
}
|
||||
|
||||
expect(response.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
content: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
author: expect.any(Object),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import app from "../../app";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import { expect, describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
|
||||
import type User from "interfaces/user";
|
||||
let user: User;
|
||||
|
||||
describe("POST /user/auth", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteUser(user.username as string);
|
||||
});
|
||||
|
||||
it("should respond with a error if the user does not exists", async () => {
|
||||
const response = await request(app)
|
||||
.post("/user/auth")
|
||||
.send({ email: "mm@mm.com", password: "aa" })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
expect(response.body.error).toBe("Invalid email or password");
|
||||
});
|
||||
|
||||
it("should respond with a error if receive an invalid email or password", async () => {
|
||||
const response = await request(app)
|
||||
.post("/user/auth")
|
||||
.send({ email: user.email, password: "fake_pass" })
|
||||
.expect(400);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
expect(response.body.error).toBe("Invalid email or password");
|
||||
});
|
||||
|
||||
it("should respond with a error if receive an empty body", async () => {
|
||||
const response = await request(app).post("/user/auth").send({}).expect(400);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
expect(response.body.error).toBe("Missing fields");
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { describe, beforeAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("DELETE /user/delete", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
it("should delete the user successfully", async () => {
|
||||
await request(app)
|
||||
.post("/user/delete")
|
||||
.set("Authorization", `Bearer ${user.token ?? ""}`)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { expect, describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("POST /user/info", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await deleteUser(user.username as string);
|
||||
});
|
||||
|
||||
it("should respond with 200 status code and return the user data", async () => {
|
||||
const response = await request(app)
|
||||
.get(`/user/info?u=${user.username ?? ""}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toHaveProperty("profileImage");
|
||||
expect(response.body).toHaveProperty("displayName");
|
||||
expect(response.body).toHaveProperty("username");
|
||||
expect(response.body).toHaveProperty("createdAt");
|
||||
expect(response.body).toHaveProperty("posts");
|
||||
expect(response.body).toHaveProperty("likedPosts");
|
||||
});
|
||||
|
||||
it("should respond with 400 status code if the user send no username", async () => {
|
||||
const response = await request(app).get("/user/info?u=").expect(400);
|
||||
|
||||
expect(response.body).toHaveProperty("error");
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
import app from "../../app";
|
||||
import { describe, beforeAll, afterAll, it } from "vitest";
|
||||
import request from "supertest";
|
||||
import deleteUser from "../utils/delete-user";
|
||||
import signUpNewUser from "../utils/create-user";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
let user: User;
|
||||
|
||||
describe("POST /user/signup", () => {
|
||||
beforeAll(async () => {
|
||||
user = await signUpNewUser();
|
||||
user.token = undefined;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await deleteUser(user.username as string);
|
||||
});
|
||||
|
||||
it("should respond with a 400 status code if sent any invalid data", async () => {
|
||||
await request(app)
|
||||
.post("/user/signup")
|
||||
.send({
|
||||
username: "username12@",
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should respond with a 400 status code for an existing username or email", async () => {
|
||||
await request(app)
|
||||
.post("/user/signup")
|
||||
.send({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should respond with a 400 status code if receive an empty body", async () => {
|
||||
await request(app).post("/user/signup").send({}).expect(400);
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import app from "../../app";
|
||||
import request from "supertest";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import type userPayload from "../../interfaces/user";
|
||||
|
||||
async function signUpNewUser(): Promise<userPayload> {
|
||||
// To avoid conflicts with existing usernames or emails
|
||||
const username = faker.internet.userName({ lastName: "doe" }).toLowerCase();
|
||||
const email = faker.internet.email();
|
||||
const password = `${faker.internet.password()}@1`;
|
||||
|
||||
await request(app)
|
||||
.post("/user/signup")
|
||||
.send({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const response = await request(app)
|
||||
.post("/user/auth")
|
||||
.send({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
return {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
token: response.body.token,
|
||||
};
|
||||
}
|
||||
|
||||
export default signUpNewUser;
|
|
@ -1,17 +0,0 @@
|
|||
import prisma from "../../clients/prisma-client";
|
||||
|
||||
export default async function deleteUser(username: string) {
|
||||
await prisma.post.deleteMany({
|
||||
where: {
|
||||
author: {
|
||||
username,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
}
|
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
12
src/app.controller.ts
Normal file
12
src/app.controller.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
19
src/app.module.ts
Normal file
19
src/app.module.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
import { UserModule } from "./user/user.module";
|
||||
import { APP_PIPE } from "@nestjs/core";
|
||||
import { ZodValidationPipe } from "nestjs-zod";
|
||||
|
||||
@Module({
|
||||
imports: [UserModule],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
AppService,
|
||||
{
|
||||
provide: APP_PIPE,
|
||||
useClass: ZodValidationPipe,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
49
src/app.ts
49
src/app.ts
|
@ -1,49 +0,0 @@
|
|||
import "dotenv/config";
|
||||
|
||||
import compression from "compression";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import limiter from "middlewares/rate-limit";
|
||||
import morganMiddleware from "middlewares/morgan";
|
||||
import router from "./routes";
|
||||
import swaggerUI from "swagger-ui-express";
|
||||
import swaggerDocument from "helpers/parse-swagger";
|
||||
import swaggerConfig from "config/swagger";
|
||||
|
||||
const app = express();
|
||||
|
||||
// TODO: test socket io, emit notifications when create one.
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(morganMiddleware);
|
||||
app.options("*", cors());
|
||||
app.use(
|
||||
cors({
|
||||
credentials: true,
|
||||
origin: process.env.CLIENT_URL,
|
||||
methods: ["GET", "POST", "PUT"],
|
||||
optionsSuccessStatus: 200,
|
||||
})
|
||||
);
|
||||
app.use(express.static("public"));
|
||||
app.use(limiter);
|
||||
app.use(router);
|
||||
app.use(
|
||||
"/docs",
|
||||
swaggerUI.serve,
|
||||
swaggerUI.setup(swaggerDocument, swaggerConfig)
|
||||
);
|
||||
app.use(compression({ level: 9 }));
|
||||
|
||||
app.get("/", (_req, res) => {
|
||||
res.redirect("/docs");
|
||||
});
|
||||
|
||||
app.use((_req, res) => {
|
||||
res.status(404).json({
|
||||
error: "Endpoint not found",
|
||||
});
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -1,5 +0,0 @@
|
|||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
|
@ -1,27 +0,0 @@
|
|||
import logger from "helpers/logger";
|
||||
import { createClient, type RedisClientOptions } from "redis";
|
||||
|
||||
const redisPort = parseInt(process.env.REDIS_PORT ?? "6379", 10);
|
||||
const redisHost = process.env.REDIS_HOST ?? "127.0.0.1";
|
||||
const redisPassword = process.env.REDIS_PASSWORD ?? "";
|
||||
|
||||
const redisConfig: RedisClientOptions = {
|
||||
url: `redis://:${redisPassword}@${redisHost}:${redisPort}/0`,
|
||||
};
|
||||
|
||||
const redis = createClient(redisConfig);
|
||||
|
||||
redis
|
||||
.connect()
|
||||
.then(() => {
|
||||
logger.info("Successfully connected to Redis");
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
logger.error(`Error while connecting to Redis: ${e.message}`);
|
||||
});
|
||||
|
||||
redis.on("error", async (e: Error) => {
|
||||
logger.error(`Error in Redis client: ${e.message}`);
|
||||
});
|
||||
|
||||
export default redis;
|
|
@ -1,24 +0,0 @@
|
|||
import { S3Client } from "@aws-sdk/client-s3";
|
||||
import logger from "helpers/logger";
|
||||
|
||||
let s3: S3Client;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
logger.info("Using Localstack services instead of AWS.");
|
||||
s3 = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
endpoint: "http://127.0.0.1:4566", // Uses localstack instead of aws, make sure to create the bucket first with public-read acl
|
||||
});
|
||||
} else {
|
||||
s3 = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default s3;
|
|
@ -1,64 +0,0 @@
|
|||
import multer from "multer";
|
||||
import { type Request } from "express";
|
||||
import path from "path";
|
||||
import s3 from "clients/s3-client";
|
||||
import multerS3 from "multer-s3";
|
||||
|
||||
const tempFolder = path.resolve(__dirname, "..", "..", "temp", "uploads");
|
||||
|
||||
const storageTypes = {
|
||||
local: multer.diskStorage({
|
||||
destination: (req: Request, file: Express.Multer.File, callback) => {
|
||||
callback(null, tempFolder);
|
||||
},
|
||||
|
||||
filename: (req: Request, file: Express.Multer.File, callback) => {
|
||||
const folder = req.body.isProfilePicture ? "profile_images" : "media";
|
||||
const fileName: string = `${folder}/${req.res?.locals.user.id}.webp`;
|
||||
|
||||
callback(null, fileName);
|
||||
},
|
||||
}),
|
||||
|
||||
s3: multerS3({
|
||||
s3,
|
||||
bucket: process.env.AWS_BUCKET ?? "",
|
||||
contentType: multerS3.AUTO_CONTENT_TYPE,
|
||||
acl: "public-read",
|
||||
key: (req: Request, file: Express.Multer.File, callback) => {
|
||||
let folder: string;
|
||||
|
||||
if (req.body.isProfilePicture === "true") {
|
||||
folder = "profile_images";
|
||||
} else {
|
||||
folder = "media";
|
||||
}
|
||||
|
||||
const fileName: string = `${folder}/${req.res?.locals.user.id}.jpg`;
|
||||
callback(null, fileName);
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const multerConfig = {
|
||||
dest: tempFolder,
|
||||
storage: storageTypes.s3,
|
||||
limits: {
|
||||
fileSize: 15 * 1024 * 1024, // 1mb
|
||||
},
|
||||
fileFilter: (
|
||||
req: Request,
|
||||
file: Express.Multer.File,
|
||||
callback: multer.FileFilterCallback
|
||||
) => {
|
||||
const allowedMimes = ["image/jpeg", "image/png"];
|
||||
|
||||
if (allowedMimes.includes(file.mimetype)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error("Filetype not allowed"));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default multerConfig;
|
|
@ -1,8 +0,0 @@
|
|||
import type { SwaggerUiOptions } from "swagger-ui-express";
|
||||
|
||||
const swaggerConfig: SwaggerUiOptions = {
|
||||
customSiteTitle: "Project Knedita Docs",
|
||||
customfavIcon: "/favicon.png",
|
||||
};
|
||||
|
||||
export default swaggerConfig;
|
|
@ -1,22 +0,0 @@
|
|||
import { Router } from "express";
|
||||
|
||||
// Controllers
|
||||
import comments from "./comments";
|
||||
|
||||
// Middlewares
|
||||
import authenticated from "middlewares/authenticated";
|
||||
|
||||
const commentsRouter = Router();
|
||||
|
||||
// GET
|
||||
commentsRouter.get("/fetch-likes", comments.fetchLikes);
|
||||
commentsRouter.get("/info", comments.fetch);
|
||||
|
||||
// POST
|
||||
commentsRouter.post("/create", authenticated, comments.create);
|
||||
commentsRouter.post("/delete", authenticated, comments.delete);
|
||||
|
||||
// PUT
|
||||
commentsRouter.put("/update", authenticated, comments.update);
|
||||
|
||||
export default commentsRouter;
|
|
@ -1,28 +0,0 @@
|
|||
import comment from "services/comments";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function commentCreateController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { content, postId } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
if (postId === undefined) {
|
||||
badRequest(res, "Expected post id");
|
||||
return;
|
||||
}
|
||||
|
||||
if (content === undefined) {
|
||||
badRequest(res, "Expected comment content");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await comment.create(postId, content, id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default commentCreateController;
|
|
@ -1,23 +0,0 @@
|
|||
import comment from "services/comments";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function commentDeleteController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { commentId } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
if (commentId === undefined) {
|
||||
badRequest(res, "Expected comment id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await comment.delete(commentId, id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default commentDeleteController;
|
|
@ -1,22 +0,0 @@
|
|||
import comment from "services/comments";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function commentFetchController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const commentId = req.query.id as string;
|
||||
|
||||
if (commentId === undefined) {
|
||||
badRequest(res, "Expected comment id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await comment.fetch(commentId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default commentFetchController;
|
|
@ -1,22 +0,0 @@
|
|||
import comment from "services/comments";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function commentFetchLikesController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const commentId = req.query.id as string;
|
||||
|
||||
if (commentId === undefined) {
|
||||
badRequest(res, "Expected comment id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await comment.fetchLikes(commentId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default commentFetchLikesController;
|
|
@ -1,15 +0,0 @@
|
|||
import commentCreateController from "./create";
|
||||
import commentDeleteController from "./delete";
|
||||
import commentFetchController from "./fetch-info";
|
||||
import commentFetchLikesController from "./fetch-likes";
|
||||
import commentUpdateController from "./update";
|
||||
|
||||
const comments = {
|
||||
create: commentCreateController,
|
||||
delete: commentDeleteController,
|
||||
fetch: commentFetchController,
|
||||
fetchLikes: commentFetchLikesController,
|
||||
update: commentUpdateController,
|
||||
} as const;
|
||||
|
||||
export default comments;
|
|
@ -1,28 +0,0 @@
|
|||
import comment from "services/comments";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function commentUpdateController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { commentId, content } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
if (commentId === undefined) {
|
||||
badRequest(res, "Expected comment content");
|
||||
return;
|
||||
}
|
||||
|
||||
if (content === undefined) {
|
||||
badRequest(res, "Expected content to update");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await comment.update(content, id, commentId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default commentUpdateController;
|
|
@ -1,22 +0,0 @@
|
|||
import { Router } from "express";
|
||||
|
||||
// Controllers
|
||||
import post from "./posts";
|
||||
|
||||
// Middlewares
|
||||
import authenticated from "middlewares/authenticated";
|
||||
|
||||
const postsRouter = Router();
|
||||
|
||||
// GET
|
||||
postsRouter.get("/fetch-likes", post.fetchLikes);
|
||||
postsRouter.get("/info", post.fetch);
|
||||
|
||||
// POST
|
||||
postsRouter.post("/create", authenticated, post.create);
|
||||
postsRouter.post("/delete", authenticated, post.delete);
|
||||
|
||||
// PUT
|
||||
postsRouter.put("/update", authenticated, post.update);
|
||||
|
||||
export default postsRouter;
|
|
@ -1,23 +0,0 @@
|
|||
import post from "services/posts";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function postCreateController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { content } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
if (content === undefined) {
|
||||
badRequest(res, "Expected post content");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await post.create(content, id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default postCreateController;
|
|
@ -1,23 +0,0 @@
|
|||
import post from "services/posts";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function postDeleteController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const userId = res.locals.user.id;
|
||||
const postId = req.body.postId;
|
||||
|
||||
if (postId === undefined) {
|
||||
badRequest(res, "Missing post id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await post.delete(postId, userId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default postDeleteController;
|
|
@ -1,22 +0,0 @@
|
|||
import post from "services/posts";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function postFetchInfoController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const id = req.query.id as string;
|
||||
|
||||
if (id === undefined) {
|
||||
badRequest(res, "Missing post id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await post.fetch(id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default postFetchInfoController;
|
|
@ -1,22 +0,0 @@
|
|||
import post from "services/posts";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function postFetchLikesController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const id = req.query.id as string;
|
||||
|
||||
if (id === undefined) {
|
||||
badRequest(res, "Missing post id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await post.fetchLikes(id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default postFetchLikesController;
|
|
@ -1,15 +0,0 @@
|
|||
import postCreateController from "./create";
|
||||
import postDeleteController from "./delete";
|
||||
import postFetchInfoController from "./fetch-info";
|
||||
import postUpdateController from "./update";
|
||||
import postFetchLikesController from "./fetch-likes";
|
||||
|
||||
const post = {
|
||||
create: postCreateController,
|
||||
delete: postDeleteController,
|
||||
fetch: postFetchInfoController,
|
||||
fetchLikes: postFetchLikesController,
|
||||
update: postUpdateController,
|
||||
} as const;
|
||||
|
||||
export default post;
|
|
@ -1,17 +0,0 @@
|
|||
import post from "services/posts";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function postUpdateController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { postId, content } = req.body;
|
||||
const userId = res.locals.user.id;
|
||||
|
||||
const result = await post.update(postId, content, userId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default postUpdateController;
|
|
@ -1,37 +0,0 @@
|
|||
import { Router } from "express";
|
||||
|
||||
// Controllers
|
||||
import user from "./users";
|
||||
|
||||
// Middlewares
|
||||
import authenticated from "middlewares/authenticated";
|
||||
import uploadFile from "middlewares/upload-image";
|
||||
|
||||
const usersRouter = Router();
|
||||
|
||||
// GET
|
||||
usersRouter.get("/fetch-posts", user.fetchPosts);
|
||||
usersRouter.get("/info", user.fetchInfo);
|
||||
usersRouter.get("/search", user.searchUser);
|
||||
|
||||
// POST
|
||||
usersRouter.post("/auth", user.auth);
|
||||
usersRouter.post("/delete", authenticated, user.delete);
|
||||
usersRouter.post("/me", authenticated, user.fetchUser);
|
||||
usersRouter.post("/follow-user", authenticated, user.follow);
|
||||
usersRouter.post("/like-comment", authenticated, user.likeComment);
|
||||
usersRouter.post("/like-post", authenticated, user.likePost);
|
||||
usersRouter.post("/signup", user.signup);
|
||||
|
||||
// PUT
|
||||
usersRouter.put(
|
||||
"/profile-picture/upload",
|
||||
authenticated,
|
||||
uploadFile,
|
||||
user.uploadPicture
|
||||
);
|
||||
usersRouter.put("/update-email", authenticated, user.updateEmail);
|
||||
usersRouter.put("/update-name", authenticated, user.updateName);
|
||||
usersRouter.put("/update-password", authenticated, user.updatePassword);
|
||||
|
||||
export default usersRouter;
|
|
@ -1,13 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userAuthController(req: Request, res: Response): Promise<void> {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const result = await user.auth({ email, password });
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userAuthController;
|
|
@ -1,15 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userDeleteController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const userId = res.locals.user.id;
|
||||
const result = await user.delete(userId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userDeleteController;
|
|
@ -1,22 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userFetchInfoController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const username = req.query.u as string;
|
||||
|
||||
if (username === undefined) {
|
||||
badRequest(res, "Missing username");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await user.fetchInfo(username.toLowerCase());
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userFetchInfoController;
|
|
@ -1,22 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userFetchPostsController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const username = req.query.u as string;
|
||||
|
||||
if (username === undefined) {
|
||||
badRequest(res, "Missing username");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await user.fetchPosts(username);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userFetchPostsController;
|
|
@ -1,22 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userFetchUserController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const id = res.locals.user.id;
|
||||
|
||||
if (id === undefined) {
|
||||
badRequest(res, "Missing id");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await user.fetchUser(id);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userFetchUserController;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userFollowController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const userId = res.locals.user.id;
|
||||
const { userToFollow } = req.body;
|
||||
|
||||
const result = await user.follow(userId, userToFollow);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userFollowController;
|
|
@ -1,33 +0,0 @@
|
|||
import userAuthController from "./auth";
|
||||
import userDeleteController from "./delete";
|
||||
import userFollowController from "./follow-user";
|
||||
import userFetchInfoController from "./fetch-info";
|
||||
import userFetchPostsController from "./fetch-posts";
|
||||
import userFetchUserController from "./fetch-user";
|
||||
import userLikeCommentController from "./like-comment";
|
||||
import userLikePostController from "./like-post";
|
||||
import userSearchController from "./search-user";
|
||||
import userSignupController from "./signup";
|
||||
import userUpdateEmailController from "./update-email";
|
||||
import userUpdateNameController from "./update-name";
|
||||
import userUpdatePasswordController from "./update-password";
|
||||
import userUploadPictureController from "./upload-picture";
|
||||
|
||||
const user = {
|
||||
auth: userAuthController,
|
||||
delete: userDeleteController,
|
||||
fetchInfo: userFetchInfoController,
|
||||
fetchPosts: userFetchPostsController,
|
||||
fetchUser: userFetchUserController,
|
||||
follow: userFollowController,
|
||||
likeComment: userLikeCommentController,
|
||||
likePost: userLikePostController,
|
||||
searchUser: userSearchController,
|
||||
signup: userSignupController,
|
||||
updateEmail: userUpdateEmailController,
|
||||
updateName: userUpdateNameController,
|
||||
updatePassword: userUpdatePasswordController,
|
||||
uploadPicture: userUploadPictureController,
|
||||
} as const;
|
||||
|
||||
export default user;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userLikeCommentController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const userId = res.locals.user.id;
|
||||
const { commentId } = req.body;
|
||||
|
||||
const result = await user.likeComment(commentId, userId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userLikeCommentController;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userLikePostController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const userId = res.locals.user.id;
|
||||
const { postId } = req.body;
|
||||
|
||||
const result = await user.likePost(postId, userId);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userLikePostController;
|
|
@ -1,21 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
|
||||
async function userSearchController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const username = req.query.u as string;
|
||||
|
||||
if (username === undefined) {
|
||||
badRequest(res, "Missing username");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await user.searchUser(username);
|
||||
|
||||
res.json(result);
|
||||
}
|
||||
|
||||
export default userSearchController;
|
|
@ -1,16 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userSignupController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
const result = await user.signup({ username, email, password });
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userSignupController;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userUpdateEmailController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { email } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
const result = await user.updateEmail({ id, email });
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userUpdateEmailController;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userUpdateNameController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { displayName, username } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
const result = await user.updateName({ id, displayName, username });
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userUpdateNameController;
|
|
@ -1,17 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userUpdatePasswordController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const id = res.locals.user.id;
|
||||
|
||||
const result = await user.updatePassword(id, currentPassword, newPassword);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userUpdatePasswordController;
|
|
@ -1,32 +0,0 @@
|
|||
import user from "services/users";
|
||||
import type { Request, Response } from "express";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
import handleResponse from "helpers/handle-response";
|
||||
|
||||
async function userUploadPictureController(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
if (req.file === undefined) {
|
||||
badRequest(res, "Expected a JPG or PNG file");
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = res.locals.user.id;
|
||||
|
||||
let url: string;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
url = `http://${
|
||||
process.env.AWS_BUCKET ?? ""
|
||||
}.s3.localhost.localstack.cloud:4566/${req.file.key}`;
|
||||
} else {
|
||||
url = req.file.location;
|
||||
}
|
||||
|
||||
const result = await user.uploadPicture(userId, url);
|
||||
|
||||
handleResponse(res, result);
|
||||
}
|
||||
|
||||
export default userUploadPictureController;
|
|
@ -1,42 +0,0 @@
|
|||
import sharp from "sharp";
|
||||
import s3 from "clients/s3-client";
|
||||
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
export default async function compressImage(
|
||||
imageName: string,
|
||||
isProfilePicture: string
|
||||
): Promise<Error | Record<never, never>> {
|
||||
// Get file from s3
|
||||
const { Body } = await s3.send(
|
||||
new GetObjectCommand({
|
||||
Bucket: process.env.AWS_BUCKET ?? "",
|
||||
Key: imageName,
|
||||
})
|
||||
);
|
||||
|
||||
const imageBuffer = await Body?.transformToByteArray();
|
||||
|
||||
const compressedImageBuffer = await sharp(imageBuffer)
|
||||
.resize(
|
||||
isProfilePicture === "true" ? 200 : undefined,
|
||||
isProfilePicture === "true" ? 200 : undefined
|
||||
)
|
||||
.jpeg({ quality: 65 })
|
||||
.toBuffer();
|
||||
|
||||
// Send file back
|
||||
const params = {
|
||||
Bucket: process.env.AWS_BUCKET ?? "",
|
||||
Key: imageName,
|
||||
Body: compressedImageBuffer,
|
||||
ContentType: "image/jpeg",
|
||||
ContentDisposition: "inline",
|
||||
};
|
||||
|
||||
const { ETag } = await s3.send(new PutObjectCommand(params));
|
||||
|
||||
if (ETag !== null) {
|
||||
return {};
|
||||
}
|
||||
return new Error("Error while compressing image");
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { type Response } from "express";
|
||||
import { badRequest } from "./http-errors";
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: don't question it.
|
||||
export default function handleResponse(res: Response, result: any): void {
|
||||
if (result instanceof Error) {
|
||||
badRequest(res, result.message);
|
||||
} else {
|
||||
res.json(result);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { type Response } from "express";
|
||||
|
||||
const sendErrorResponse = (
|
||||
res: Response,
|
||||
status: number,
|
||||
message: string
|
||||
): void => {
|
||||
res.status(status).json({ error: message });
|
||||
};
|
||||
|
||||
export const badRequest = (res: Response, message = "Bad Request"): void => {
|
||||
sendErrorResponse(res, 400, message);
|
||||
};
|
||||
|
||||
export const unauthorized = (res: Response, message = "Unauthorized"): void => {
|
||||
sendErrorResponse(res, 401, message);
|
||||
};
|
||||
|
||||
export const forbidden = (res: Response, message = "Forbidden"): void => {
|
||||
sendErrorResponse(res, 403, message);
|
||||
};
|
||||
|
||||
export const internalServerError = (
|
||||
res: Response,
|
||||
message = "Internal Server Error"
|
||||
): void => {
|
||||
sendErrorResponse(res, 500, message);
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
import winston from "winston";
|
||||
|
||||
const levels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
const level = (): string => {
|
||||
const env = process.env.NODE_ENV || "development";
|
||||
const isDevelopment = env === "development";
|
||||
return isDevelopment ? "debug" : "warn";
|
||||
};
|
||||
|
||||
const colors = {
|
||||
error: "red",
|
||||
warn: "yellow",
|
||||
info: "green",
|
||||
http: "magenta",
|
||||
debug: "white",
|
||||
};
|
||||
|
||||
winston.addColors(colors);
|
||||
|
||||
const format = winston.format.combine(
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf(
|
||||
(info) => `${info.timestamp} ${info.level}: ${info.message}`
|
||||
)
|
||||
);
|
||||
|
||||
const transports = [
|
||||
new winston.transports.Console(),
|
||||
new winston.transports.File({
|
||||
filename: "logs/error.log",
|
||||
level: "error",
|
||||
}),
|
||||
];
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: level(),
|
||||
levels,
|
||||
format,
|
||||
transports,
|
||||
});
|
||||
|
||||
export default logger;
|
|
@ -1,49 +0,0 @@
|
|||
import { type NotificationType } from "@prisma/client";
|
||||
import prisma from "clients/prisma-client";
|
||||
|
||||
export async function createNotification(
|
||||
fromUserId: string,
|
||||
toUserId: string,
|
||||
content: string,
|
||||
type: NotificationType
|
||||
): Promise<Record<never, never> | Error> {
|
||||
try {
|
||||
await prisma.notifications.create({
|
||||
data: {
|
||||
type,
|
||||
fromUserId,
|
||||
toUserId,
|
||||
content,
|
||||
},
|
||||
include: {
|
||||
fromUser: {
|
||||
select: {
|
||||
id: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {};
|
||||
} catch (_) {
|
||||
return new Error("Error while creating notification");
|
||||
}
|
||||
}
|
||||
|
||||
export async function countNotifications(
|
||||
toUserId: string
|
||||
): Promise<number | Error> {
|
||||
try {
|
||||
const count = await prisma.notifications.count({
|
||||
where: {
|
||||
toUserId,
|
||||
},
|
||||
});
|
||||
|
||||
return count;
|
||||
} catch (_) {
|
||||
return new Error("Error while counting user notifications");
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { parse } from "yaml";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const swaggerConfigFile = readFileSync("./swagger.yaml", "utf-8");
|
||||
const swaggerDocument = parse(swaggerConfigFile);
|
||||
|
||||
export default swaggerDocument;
|
|
@ -1,7 +0,0 @@
|
|||
interface jwtPayload {
|
||||
id: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
export default jwtPayload;
|
|
@ -1,13 +0,0 @@
|
|||
interface User {
|
||||
id?: string;
|
||||
displayName?: string | null;
|
||||
username?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
profileImage?: string | null;
|
||||
createdAt?: Date;
|
||||
token?: string;
|
||||
socketId?: string;
|
||||
}
|
||||
|
||||
export default User;
|
28
src/main.ts
Normal file
28
src/main.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { NestFactory } from "@nestjs/core";
|
||||
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
|
||||
import { AppModule } from "./app.module";
|
||||
import { patchNestJsSwagger } from "nestjs-zod";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
patchNestJsSwagger();
|
||||
|
||||
app.enableCors();
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle("Project Knedita")
|
||||
.setDescription("An open-source social media")
|
||||
.setVersion("1.0")
|
||||
.addTag("User")
|
||||
.addTag("Post")
|
||||
.addTag("Comment")
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
|
||||
SwaggerModule.setup("/", app, document);
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
|
@ -1,56 +0,0 @@
|
|||
import { verify } from "jsonwebtoken";
|
||||
import prisma from "clients/prisma-client";
|
||||
import type { Response, Request, NextFunction } from "express";
|
||||
import { unauthorized } from "helpers/http-errors";
|
||||
import type jwtPayload from "interfaces/jwt";
|
||||
|
||||
async function authenticated(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (
|
||||
req.headers.authorization === undefined ||
|
||||
req.headers.authorization.length === 0
|
||||
) {
|
||||
unauthorized(res, "Missing token");
|
||||
return;
|
||||
}
|
||||
|
||||
const token = req.headers.authorization.split(" ")[1];
|
||||
|
||||
if (token === undefined) {
|
||||
unauthorized(res, "Missing token");
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = verify(
|
||||
token,
|
||||
process.env.JWT_ACCESS_SECRET ?? ""
|
||||
) as jwtPayload;
|
||||
|
||||
if (id === undefined) {
|
||||
unauthorized(res, "Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === undefined) {
|
||||
unauthorized(res, "User does not exists");
|
||||
return;
|
||||
}
|
||||
|
||||
res.locals.user = user;
|
||||
next();
|
||||
} catch (e) {
|
||||
unauthorized(res, `JWT Error: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default authenticated;
|
|
@ -1,12 +0,0 @@
|
|||
import morgan, { type StreamOptions } from "morgan";
|
||||
import logger from "helpers/logger";
|
||||
|
||||
const stream: StreamOptions = {
|
||||
write: (message) => logger.http(message),
|
||||
};
|
||||
|
||||
const morganMiddleware = morgan(":method :url :status - :response-time ms", {
|
||||
stream,
|
||||
});
|
||||
|
||||
export default morganMiddleware;
|
|
@ -1,26 +0,0 @@
|
|||
import rateLimit from "express-rate-limit";
|
||||
import RedisStore from "rate-limit-redis";
|
||||
import redis from "clients/redis-client";
|
||||
import logger from "helpers/logger";
|
||||
|
||||
let skip: boolean;
|
||||
|
||||
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
|
||||
logger.info("Development environment detected. Rate limit is now disabled.");
|
||||
skip = true;
|
||||
} else {
|
||||
skip = false;
|
||||
}
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 1 * 60 * 1000, // 60 seconds
|
||||
max: 5,
|
||||
message: { error: "Too many requests" },
|
||||
legacyHeaders: false,
|
||||
skip: (_req, _res) => skip,
|
||||
store: new RedisStore({
|
||||
sendCommand: async (...args: string[]) => await redis.sendCommand(args),
|
||||
}),
|
||||
});
|
||||
|
||||
export default limiter;
|
|
@ -1,33 +0,0 @@
|
|||
import type { Response, Request, NextFunction } from "express";
|
||||
import multer from "multer";
|
||||
import multerConfig from "config/multer";
|
||||
import compressImage from "helpers/compress-image";
|
||||
import { badRequest } from "helpers/http-errors";
|
||||
|
||||
function uploadImage(req: Request, res: Response, next: NextFunction): void {
|
||||
const upload = multer(multerConfig).single("image");
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: i don't know, it's working, no need to change anything here.
|
||||
upload(req, res, async (cb: multer.MulterError | Error | any) => {
|
||||
if (req.res?.locals.user == null) {
|
||||
badRequest(res, "You must be logged in to upload a profile picture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cb instanceof multer.MulterError || cb instanceof Error) {
|
||||
badRequest(res, cb.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.file === undefined) {
|
||||
badRequest(res, "Expected file");
|
||||
return;
|
||||
}
|
||||
|
||||
await compressImage(req.file?.key, req.body.isProfilePicture);
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
export default uploadImage;
|
|
@ -1,14 +0,0 @@
|
|||
import { Router } from "express";
|
||||
|
||||
// Routers
|
||||
import usersRouter from "controllers/users-router";
|
||||
import postsRouter from "controllers/posts-router";
|
||||
import commentsRouter from "controllers/comments-router";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use("/user", usersRouter);
|
||||
router.use("/post", postsRouter);
|
||||
router.use("/comment", commentsRouter);
|
||||
|
||||
export default router;
|
|
@ -1,19 +0,0 @@
|
|||
import app from "./app";
|
||||
import { createServer } from "http";
|
||||
import logger from "helpers/logger";
|
||||
|
||||
import prisma from "clients/prisma-client";
|
||||
import redis from "clients/redis-client";
|
||||
|
||||
const server = createServer(app);
|
||||
|
||||
server.listen(process.env.SERVER_PORT, () => {
|
||||
logger.info(`Server is running @ ${process.env.SERVER_PORT ?? ""}`);
|
||||
});
|
||||
|
||||
process.on("SIGINT", async () => {
|
||||
logger.warn("Closing server...");
|
||||
await prisma.$disconnect();
|
||||
await redis.disconnect();
|
||||
server.close();
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function commentCreateService(
|
||||
postId: string,
|
||||
content: string,
|
||||
authorId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const post = await prisma.post.findFirst({
|
||||
where: {
|
||||
id: postId,
|
||||
},
|
||||
});
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: authorId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const comment = await prisma.comments.create({
|
||||
data: {
|
||||
content,
|
||||
postId,
|
||||
userId: authorId,
|
||||
},
|
||||
});
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
export default commentCreateService;
|
|
@ -1,38 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function commentDeleteService(
|
||||
commentId: string,
|
||||
authorId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: authorId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const comment = await prisma.comments.findFirst({
|
||||
where: {
|
||||
id: commentId,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (comment === null) {
|
||||
return new Error("Comment not found");
|
||||
}
|
||||
|
||||
await prisma.comments.deleteMany({
|
||||
where: {
|
||||
id: comment.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export default commentDeleteService;
|
|
@ -1,33 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function commentFetchService(
|
||||
commentId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const comment = await prisma.comments.findFirst({
|
||||
where: {
|
||||
id: commentId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (comment === null) {
|
||||
return new Error("Comment not found");
|
||||
}
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
export default commentFetchService;
|
|
@ -1,26 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function commentFetchLikesService(id: string): Promise<unknown | Error> {
|
||||
const post = await prisma.commentLike.findMany({
|
||||
where: {
|
||||
commentId: id,
|
||||
},
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Comment not found");
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
export default commentFetchLikesService;
|
|
@ -1,15 +0,0 @@
|
|||
import commentCreateService from "./create";
|
||||
import commentDeleteService from "./delete";
|
||||
import commentFetchService from "./fetch-info";
|
||||
import commentFetchLikesService from "./fetch-likes";
|
||||
import commentUpdateService from "./update";
|
||||
|
||||
const comment = {
|
||||
create: commentCreateService,
|
||||
delete: commentDeleteService,
|
||||
fetch: commentFetchService,
|
||||
fetchLikes: commentFetchLikesService,
|
||||
update: commentUpdateService,
|
||||
} as const;
|
||||
|
||||
export default comment;
|
|
@ -1,44 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function commentUpdateService(
|
||||
content: string,
|
||||
authorId: string,
|
||||
commentId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const comment = await prisma.comments.findFirst({
|
||||
where: {
|
||||
id: commentId,
|
||||
userId: authorId,
|
||||
},
|
||||
});
|
||||
|
||||
if (comment === null) {
|
||||
return new Error("Comment does not exists");
|
||||
}
|
||||
|
||||
const updatedComment = await prisma.comments.update({
|
||||
where: {
|
||||
id: comment.id,
|
||||
userId: authorId,
|
||||
},
|
||||
data: {
|
||||
content,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
user: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return updatedComment;
|
||||
}
|
||||
|
||||
export default commentUpdateService;
|
|
@ -1,23 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function postCreateService(
|
||||
content: string,
|
||||
authorId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({ where: { id: authorId } });
|
||||
|
||||
if (user === null) {
|
||||
return new Error("This user doesn't exists");
|
||||
}
|
||||
|
||||
const post = await prisma.post.create({
|
||||
data: {
|
||||
content,
|
||||
authorId,
|
||||
},
|
||||
});
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
export default postCreateService;
|
|
@ -1,30 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function postDeleteService(
|
||||
postId: string,
|
||||
userId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const post = await prisma.post.findFirst({ where: { id: postId } });
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
if ((await prisma.user.findFirst({ where: { id: userId } })) === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
if (post.authorId !== userId) {
|
||||
return new Error("Forbidden");
|
||||
}
|
||||
|
||||
await prisma.post.deleteMany({
|
||||
where: {
|
||||
id: postId,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export default postDeleteService;
|
|
@ -1,33 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function postFetchInfoService(
|
||||
id: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const post = await prisma.post.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
likes: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
export default postFetchInfoService;
|
|
@ -1,26 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function postFetchLikesService(id: string): Promise<unknown | Error> {
|
||||
const post = await prisma.postLike.findMany({
|
||||
where: {
|
||||
postId: id,
|
||||
},
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
export default postFetchLikesService;
|
|
@ -1,15 +0,0 @@
|
|||
import postCreateService from "./create";
|
||||
import postDeleteService from "./delete";
|
||||
import postFetchInfoService from "./fetch-info";
|
||||
import postFetchLikesService from "./fetch-likes";
|
||||
import postUpdateService from "./update";
|
||||
|
||||
const post = {
|
||||
create: postCreateService,
|
||||
delete: postDeleteService,
|
||||
fetch: postFetchInfoService,
|
||||
fetchLikes: postFetchLikesService,
|
||||
update: postUpdateService,
|
||||
} as const;
|
||||
|
||||
export default post;
|
|
@ -1,50 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function postUpdateService(
|
||||
postId: string,
|
||||
content: string,
|
||||
userId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const post = await prisma.post.findFirst({ where: { id: postId } });
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
if ((await prisma.user.findFirst({ where: { id: userId } })) === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
if (post.authorId !== userId) {
|
||||
return new Error("Forbidden");
|
||||
}
|
||||
|
||||
if (post.content === content.trim()) {
|
||||
let postContent: string = content;
|
||||
postContent = post.content;
|
||||
}
|
||||
|
||||
const updatedPost = await prisma.post.update({
|
||||
where: {
|
||||
id: postId,
|
||||
},
|
||||
data: {
|
||||
content,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return updatedPost;
|
||||
}
|
||||
export default postUpdateService;
|
|
@ -1,47 +0,0 @@
|
|||
import * as bcrypt from "bcrypt";
|
||||
import jsonwebtoken from "jsonwebtoken";
|
||||
import prisma from "clients/prisma-client";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
async function userAuthService({
|
||||
email,
|
||||
password,
|
||||
}: User): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
return new Error("Invalid email or password");
|
||||
}
|
||||
|
||||
if (email === undefined || password === undefined) {
|
||||
return new Error("Missing fields");
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(
|
||||
password.replace(/ /g, ""),
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!validPassword) {
|
||||
return new Error("Invalid email or password");
|
||||
}
|
||||
|
||||
const { id } = user;
|
||||
|
||||
const bearer = jsonwebtoken.sign(
|
||||
{ id },
|
||||
process.env.JWT_ACCESS_SECRET ?? "",
|
||||
{ expiresIn: "1d" }
|
||||
);
|
||||
|
||||
return {
|
||||
token: bearer,
|
||||
user: user.username,
|
||||
};
|
||||
}
|
||||
|
||||
export default userAuthService;
|
|
@ -1,25 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userDeleteService(
|
||||
userId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({ where: { id: userId } });
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
if (user.id !== userId) {
|
||||
return new Error("Forbidden");
|
||||
}
|
||||
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export default userDeleteService;
|
|
@ -1,50 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userFetchInfoService(
|
||||
username: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
profileImage: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
followers: true,
|
||||
following: true,
|
||||
posts: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
},
|
||||
likedPosts: {
|
||||
select: {
|
||||
postId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const followers = user.followers.length;
|
||||
const following = user.following.length;
|
||||
|
||||
const info = {
|
||||
...user,
|
||||
followers,
|
||||
following,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
export default userFetchInfoService;
|
|
@ -1,30 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userFetchPostsService(
|
||||
username: string
|
||||
): Promise<unknown | Error> {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
author: {
|
||||
username,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
_count: true,
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
author: {
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
export default userFetchPostsService;
|
|
@ -1,50 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userFetchUserService(
|
||||
id: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
profileImage: true,
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
followers: true,
|
||||
following: true,
|
||||
posts: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
},
|
||||
likedPosts: {
|
||||
select: {
|
||||
postId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const followers = user.followers.length;
|
||||
const following = user.following.length;
|
||||
|
||||
const info = {
|
||||
...user,
|
||||
followers,
|
||||
following,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
export default userFetchUserService;
|
|
@ -1,58 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userFollowService(
|
||||
userId: string,
|
||||
followingUsername: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
if (userId === undefined || followingUsername === undefined) {
|
||||
return new Error("Missing fields");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: followingUsername,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const userToFollow = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (userToFollow === null) {
|
||||
return new Error("User to follow not found");
|
||||
}
|
||||
|
||||
const alreadyFollow = await prisma.follows.findFirst({
|
||||
where: {
|
||||
followerId: user.id,
|
||||
followingId: userToFollow.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (alreadyFollow !== null) {
|
||||
await prisma.follows.deleteMany({
|
||||
where: {
|
||||
followerId: user.id,
|
||||
followingId: userToFollow.id,
|
||||
},
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
const follow = await prisma.follows.create({
|
||||
data: {
|
||||
followerId: user.id,
|
||||
followingId: userToFollow.id,
|
||||
},
|
||||
});
|
||||
|
||||
return follow;
|
||||
}
|
||||
|
||||
export default userFollowService;
|
|
@ -1,33 +0,0 @@
|
|||
import userAuthService from "./auth";
|
||||
import userDeleteService from "./delete";
|
||||
import userFollowService from "./follow-user";
|
||||
import userFetchPostsService from "./fetch-posts";
|
||||
import userFetchInfoService from "./fetch-info";
|
||||
import userFetchUserService from "./fetch-user";
|
||||
import userLikeCommentService from "./like-comment";
|
||||
import userLikePostService from "./like-post";
|
||||
import userSearchService from "./search-user";
|
||||
import userSignupService from "./signup";
|
||||
import userUpdateEmailService from "./update-email";
|
||||
import userUpdateNameService from "./update-name";
|
||||
import userUpdatePasswordService from "./update-password";
|
||||
import userUploadPictureService from "./upload-picture";
|
||||
|
||||
const user = {
|
||||
auth: userAuthService,
|
||||
delete: userDeleteService,
|
||||
fetchInfo: userFetchInfoService,
|
||||
fetchPosts: userFetchPostsService,
|
||||
fetchUser: userFetchUserService,
|
||||
follow: userFollowService,
|
||||
likeComment: userLikeCommentService,
|
||||
likePost: userLikePostService,
|
||||
searchUser: userSearchService,
|
||||
signup: userSignupService,
|
||||
updateEmail: userUpdateEmailService,
|
||||
updateName: userUpdateNameService,
|
||||
updatePassword: userUpdatePasswordService,
|
||||
uploadPicture: userUploadPictureService,
|
||||
} as const;
|
||||
|
||||
export default user;
|
|
@ -1,58 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userLikeCommentService(
|
||||
commentId: string,
|
||||
userId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
if (commentId === undefined || userId === undefined) {
|
||||
return new Error("Missing fields");
|
||||
}
|
||||
|
||||
const comment = await prisma.comments.findFirst({
|
||||
where: {
|
||||
id: commentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (comment === null) {
|
||||
return new Error("Comment not found");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const alreadyLiked = await prisma.commentLike.findFirst({
|
||||
where: {
|
||||
commentId: comment.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (alreadyLiked !== null) {
|
||||
await prisma.commentLike.deleteMany({
|
||||
where: {
|
||||
commentId: comment.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
const like = await prisma.commentLike.create({
|
||||
data: {
|
||||
commentId: comment.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return like;
|
||||
}
|
||||
|
||||
export default userLikeCommentService;
|
|
@ -1,58 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userLikePostService(
|
||||
postId: string,
|
||||
userId: string
|
||||
): Promise<Record<string, unknown> | Error> {
|
||||
if (postId === undefined || userId === undefined) {
|
||||
return new Error("Missing fields");
|
||||
}
|
||||
|
||||
const post = await prisma.post.findFirst({
|
||||
where: {
|
||||
id: postId,
|
||||
},
|
||||
});
|
||||
|
||||
if (post === null) {
|
||||
return new Error("Post not found");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
const alreadyLiked = await prisma.postLike.findFirst({
|
||||
where: {
|
||||
postId: post.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (alreadyLiked !== null) {
|
||||
await prisma.postLike.deleteMany({
|
||||
where: {
|
||||
postId: post.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
const like = await prisma.postLike.create({
|
||||
data: {
|
||||
postId: post.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return like;
|
||||
}
|
||||
|
||||
export default userLikePostService;
|
|
@ -1,20 +0,0 @@
|
|||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userSearchService(username: string): Promise<unknown | Error> {
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
username: {
|
||||
contains: username,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
profileImage: true,
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
export default userSearchService;
|
|
@ -1,61 +0,0 @@
|
|||
import * as bcrypt from "bcrypt";
|
||||
import validator from "validator";
|
||||
import prisma from "clients/prisma-client";
|
||||
import type User from "interfaces/user";
|
||||
|
||||
const passwordRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*_])[a-zA-Z0-9!@#$%^&*_]{8,}$/;
|
||||
const usernameRegex = /^[a-zA-Z0-9_.]{5,15}$/;
|
||||
|
||||
async function userSignupService({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
}: User): Promise<Record<string, unknown> | Error> {
|
||||
if (username === undefined || email === undefined || password === undefined) {
|
||||
return new Error("Missing fields");
|
||||
}
|
||||
|
||||
if (!passwordRegex.test(password)) {
|
||||
return new Error(
|
||||
"Password must have at least 8 characters, one number and one special character."
|
||||
);
|
||||
}
|
||||
|
||||
if (!usernameRegex.test(username)) {
|
||||
return new Error(
|
||||
"Username not allowed. Only alphanumerics characters (uppercase and lowercase words), underscore, dots and it must be between 5 and 15 characters"
|
||||
);
|
||||
}
|
||||
|
||||
if (!validator.isEmail(email)) {
|
||||
return new Error("Invalid email format");
|
||||
}
|
||||
|
||||
if ((await prisma.user.findFirst({ where: { username } })) != null) {
|
||||
return new Error("Username already in use");
|
||||
}
|
||||
|
||||
if ((await prisma.user.findFirst({ where: { email } })) != null) {
|
||||
return new Error("Email already in use");
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(15);
|
||||
const hashedPassword = await bcrypt.hash(password.replace(/ /g, ""), salt); // Removes every space in the string
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: username.toLowerCase(),
|
||||
email,
|
||||
password: hashedPassword,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export default userSignupService;
|
|
@ -1,42 +0,0 @@
|
|||
import type User from "interfaces/user";
|
||||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userUpdateEmailService({
|
||||
id,
|
||||
email,
|
||||
}: User): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({ where: { id } });
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
if (user.id !== id) {
|
||||
return new Error("Forbidden");
|
||||
}
|
||||
|
||||
if (email !== undefined && email.trim() !== user.email) {
|
||||
const existingUser = await prisma.user.findFirst({ where: { email } });
|
||||
if (existingUser != null && existingUser.email !== user.email) {
|
||||
return new Error("Email already in use");
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
email: email ?? user.email,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { message: "Successfully updated user email" };
|
||||
}
|
||||
|
||||
export default userUpdateEmailService;
|
|
@ -1,47 +0,0 @@
|
|||
import type User from "interfaces/user";
|
||||
import prisma from "clients/prisma-client";
|
||||
|
||||
async function userUpdateNameService({
|
||||
id,
|
||||
displayName,
|
||||
username,
|
||||
}: User): Promise<Record<string, unknown> | Error> {
|
||||
const user = await prisma.user.findFirst({ where: { id } });
|
||||
|
||||
if (user === null) {
|
||||
return new Error("User not found");
|
||||
}
|
||||
|
||||
// Check if the provided username is different from the current user's data
|
||||
// if different, queries are made to check if the new username is already in use.
|
||||
|
||||
if (username !== undefined && username.trim() !== user.username) {
|
||||
const existingUser = await prisma.user.findFirst({ where: { username } });
|
||||
if (existingUser != null && existingUser.username !== user.username) {
|
||||
return new Error("Username already in use");
|
||||
}
|
||||
}
|
||||
|
||||
if (user.id !== id) {
|
||||
return new Error("Forbidden");
|
||||
}
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
displayName: displayName ?? user.displayName,
|
||||
username: username ?? user.username,
|
||||
},
|
||||
select: {
|
||||
displayName: true,
|
||||
username: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
export default userUpdateNameService;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue