Added ESLint and Prettier, added module resolvers, tests not working by now.

This commit is contained in:
Hackntosh 2023-07-31 20:18:07 -03:00
parent fc7ede917c
commit e11771a62b
77 changed files with 1534 additions and 1584 deletions

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

18
.eslintrc.json Normal file
View file

@ -0,0 +1,18 @@
{
"env": {
"es2021": true,
"node": true
},
"extends": "standard-with-typescript",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": [
"./tsconfig.json"
]
},
"rules": {
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/explicit-function-return-type": 1
}
}

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"semi": false,
"singleQuote": true,
"arrowParens": "avoid",
"useTabs": false,
"endOfLine": "lf",
"tabWidth": 2
}

33
.swcrc
View file

@ -1,20 +1,17 @@
{ {
"jsc": { "jsc": {
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript",
"tsx": false, "tsx": false,
"decorators": true, "decorators": true,
"dynamicImport": true "dynamicImport": true
},
"target": "es2020",
"baseUrl": "./"
}, },
"exclude": [ "target": "es2020",
"@types/", "baseUrl": "./"
"interfaces/" },
], "exclude": ["@types/", "interfaces/"],
"module": { "module": {
"type": "commonjs" "type": "commonjs"
}, },
"minify": true "minify": true
} }

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"json.schemaDownload.enable": true
}

View file

@ -1,4 +1,3 @@
# Project Knedita # Project Knedita
An open-source social media. An open-source social media.
@ -21,7 +20,6 @@ An open-source social media.
- Image compression ✅ - Image compression ✅
- Following/unfollowing features ✅ - Following/unfollowing features ✅
- Like posts ✅ - Like posts ✅
- Probably pinned posts
- Authentication ✅ - Authentication ✅
- Add more verification (like, if the password is too short) ✅ - Add more verification (like, if the password is too short) ✅
- Set display name ✅ - Set display name ✅

View file

@ -11,7 +11,7 @@ services:
- .env - .env
volumes: volumes:
- postgres:/var/lib/postgresql/data - postgres:/var/lib/postgresql/data
redis: redis:
image: redis:alpine image: redis:alpine
restart: unless-stopped restart: unless-stopped
@ -24,4 +24,4 @@ services:
volumes: volumes:
postgres: postgres:
name: backend-db name: backend-db

View file

@ -31,7 +31,7 @@ services:
- .env - .env
volumes: volumes:
- postgres:/var/lib/postgresql/data - postgres:/var/lib/postgresql/data
localstack: localstack:
image: localstack/localstack image: localstack/localstack
container_name: localstack_main container_name: localstack_main
@ -45,7 +45,7 @@ services:
- .env - .env
volumes: volumes:
- localstack:/data - localstack:/data
- "/var/run/docker.sock:/var/run/docker.sock" - '/var/run/docker.sock:/var/run/docker.sock'
redis: redis:
image: redis:alpine image: redis:alpine
@ -62,4 +62,4 @@ volumes:
name: backend-db name: backend-db
redis: redis:
driver: local driver: local
localstack: localstack:

2109
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,12 @@
"description": "A social media", "description": "A social media",
"scripts": { "scripts": {
"build": "swc src -d dist", "build": "swc src -d dist",
"dev:start": "ts-node-dev --transpile-only --respawn src/server.ts", "dev:start": "ts-node-dev -r tsconfig-paths/register --transpile-only --respawn src/server.ts",
"docker": "docker compose up -d", "docker": "docker compose up -d",
"docker:build": "docker build -t api . && docker compose up -d", "docker:build": "docker build -t api . && docker compose up -d",
"docker:db": "docker compose -f docker-compose.db.yml up -d", "docker:db": "docker compose -f docker-compose.db.yml up -d",
"docker:seed": "docker exec -it api npm run prisma:seed", "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": "prisma migrate dev",
"migrate:dev:create": "prisma migrate dev --create-only", "migrate:dev:create": "prisma migrate dev --create-only",
"migrate:reset": "prisma migrate reset", "migrate:reset": "prisma migrate reset",
@ -43,16 +44,24 @@
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@types/validator": "^13.7.17", "@types/validator": "^13.7.17",
"@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"eslint-config-standard-with-typescript": "^37.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.5.0", "jest": "^29.5.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"pm2": "^4.2.3", "pm2": "^4.2.3",
"prettier": "^3.0.0",
"prisma": "^5.0.0", "prisma": "^5.0.0",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"ts-standard": "^12.0.2", "tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3" "typescript": "^5.1.6"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^5.0.0", "@prisma/client": "^5.0.0",

View file

@ -45,6 +45,8 @@ model PostLike {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
} }
// I should join these two up? Yeah, but I will not do it since it didn't work on the first time.
model CommentLike { model CommentLike {
id String @id @default(uuid()) id String @id @default(uuid())
commentId String commentId String

View file

@ -4,6 +4,8 @@ const redisPassword = process.env.REDIS_PASSWORD ?? ''
const redisHost = process.env.REDIS_HOST ?? '' const redisHost = process.env.REDIS_HOST ?? ''
const redisPort = process.env.REDIS_PORT ?? '' const redisPort = process.env.REDIS_PORT ?? ''
const redis = new RedisClient(`redis://:${redisPassword}@${redisHost}:${redisPort}/0`) const redis = new RedisClient(
`redis://:${redisPassword}@${redisHost}:${redisPort}/0`
)
export default redis export default redis

View file

@ -1,7 +1,7 @@
import multer from 'multer' import multer from 'multer'
import { Request } from 'express' import { type Request } from 'express'
import path from 'path' import path from 'path'
import s3 from '../clients/s3-client' import s3 from 'clients/s3-client'
import multerS3 from 'multer-s3' import multerS3 from 'multer-s3'
const tempFolder = path.resolve(__dirname, '..', '..', 'temp', 'uploads') const tempFolder = path.resolve(__dirname, '..', '..', 'temp', 'uploads')
@ -18,7 +18,7 @@ const storageTypes = {
const fileName: string = `${folder}/${req.user!.id}.webp` const fileName: string = `${folder}/${req.user!.id}.webp`
callback(null, fileName) callback(null, fileName)
} },
}), }),
s3: multerS3({ s3: multerS3({
@ -37,21 +37,22 @@ const storageTypes = {
const fileName: string = `${folder}/${req.user!.id}.jpg` const fileName: string = `${folder}/${req.user!.id}.jpg`
callback(null, fileName) callback(null, fileName)
} },
}) }),
} }
const multerConfig = { const multerConfig = {
dest: tempFolder, dest: tempFolder,
storage: storageTypes.s3, storage: storageTypes.s3,
limits: { limits: {
fileSize: 15 * 1024 * 1024 // 1mb fileSize: 15 * 1024 * 1024, // 1mb
}, },
fileFilter: (req: Request, file: Express.Multer.File, callback: multer.FileFilterCallback) => { fileFilter: (
const allowedMimes = [ req: Request,
'image/jpeg', file: Express.Multer.File,
'image/png' callback: multer.FileFilterCallback,
] ) => {
const allowedMimes = ['image/jpeg', 'image/png']
if (allowedMimes.includes(file.mimetype)) { if (allowedMimes.includes(file.mimetype)) {
callback(null, true) callback(null, true)

View file

@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Router } from 'express' import { Router } from 'express'
// Controllers // Controllers
import comments from './comments' import comments from './comments'
// Middlewares // Middlewares
import authenticated from '../middlewares/authenticated' import authenticated from 'middlewares/authenticated'
const commentsRouter = Router() const commentsRouter = Router()

View file

@ -1,23 +1,26 @@
import comment from '../../services/comments' import comment from 'services/comments'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function commentCreateController (req: Request, res: Response): Promise<void> { async function commentCreateController (
req: Request,
res: Response
): Promise<void> {
const { content, postId } = req.body const { content, postId } = req.body
const id = req.user?.id ?? '' const id = req.user?.id ?? ''
if (postId === undefined) { if (postId === undefined) {
return badRequest(res, 'Expected post id') badRequest(res, 'Expected post id'); return
} }
if (content === undefined) { if (content === undefined) {
return badRequest(res, 'Expected comment content') badRequest(res, 'Expected comment content'); return
} }
const result = await comment.create(postId, content, id) const result = await comment.create(postId, content, id)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,19 +1,22 @@
import comment from '../../services/comments' import comment from 'services/comments'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function commentDeleteController (req: Request, res: Response): Promise<void> { async function commentDeleteController (
req: Request,
res: Response
): Promise<void> {
const { commentId } = req.body const { commentId } = req.body
const id = req.user?.id ?? '' const id = req.user?.id ?? ''
if (commentId === undefined) { if (commentId === undefined) {
return badRequest(res, 'Expected comment id') badRequest(res, 'Expected comment id'); return
} }
const result = await comment.delete(commentId, id) const result = await comment.delete(commentId, id)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,18 +1,21 @@
import comment from '../../services/comments' import comment from 'services/comments'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function commentFetchController (req: Request, res: Response): Promise<void> { async function commentFetchController (
req: Request,
res: Response
): Promise<void> {
const commentId = req.query.id as string const commentId = req.query.id as string
if (commentId === undefined) { if (commentId === undefined) {
return badRequest(res, 'Expected comment id') badRequest(res, 'Expected comment id'); return
} }
const result = await comment.fetch(commentId) const result = await comment.fetch(commentId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,18 +1,21 @@
import comment from '../../services/comments' import comment from 'services/comments'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function commentFetchLikesController (req: Request, res: Response): Promise<void> { async function commentFetchLikesController (
req: Request,
res: Response
): Promise<void> {
const commentId = req.query.id as string const commentId = req.query.id as string
if (commentId === undefined) { if (commentId === undefined) {
return badRequest(res, 'Expected comment id') badRequest(res, 'Expected comment id'); return
} }
const result = await comment.fetchLikes(commentId) const result = await comment.fetchLikes(commentId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,23 +1,26 @@
import comment from '../../services/comments' import comment from 'services/comments'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function commentUpdateController (req: Request, res: Response): Promise<void> { async function commentUpdateController (
req: Request,
res: Response
): Promise<void> {
const { commentId, content } = req.body const { commentId, content } = req.body
const id = req.user?.id ?? '' const id = req.user?.id ?? ''
if (commentId === undefined) { if (commentId === undefined) {
return badRequest(res, 'Expected comment content') badRequest(res, 'Expected comment content'); return
} }
if (content === undefined) { if (content === undefined) {
return badRequest(res, 'Expected content to update') badRequest(res, 'Expected content to update'); return
} }
const result = await comment.update(content, id, commentId) const result = await comment.update(content, id, commentId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Router } from 'express' import { Router } from 'express'
// Controllers // Controllers
import post from './posts' import post from './posts'
// Middlewares // Middlewares
import authenticated from '../middlewares/authenticated' import authenticated from 'middlewares/authenticated'
const postsRouter = Router() const postsRouter = Router()

View file

@ -1,19 +1,22 @@
import post from '../../services/posts' import post from 'services/posts'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function postCreateController (req: Request, res: Response): Promise<void> { async function postCreateController (
req: Request,
res: Response
): Promise<void> {
const { content } = req.body const { content } = req.body
const id: string = req.user?.id ?? '' const id: string = req.user?.id ?? ''
if (content === undefined) { if (content === undefined) {
return badRequest(res, 'Expected post content') badRequest(res, 'Expected post content'); return
} }
const result = await post.create(content, id) const result = await post.create(content, id)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,15 +1,18 @@
import post from '../../services/posts' import post from 'services/posts'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function postDeleteController (req: Request, res: Response): Promise<void> { async function postDeleteController (
req: Request,
res: Response
): Promise<void> {
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const postId = req.body.postId const postId = req.body.postId
const result = await post.delete(postId, userId) const result = await post.delete(postId, userId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,18 +1,21 @@
import post from '../../services/posts' import post from 'services/posts'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function postFetchInfoController (req: Request, res: Response): Promise<void> { async function postFetchInfoController (
req: Request,
res: Response
): Promise<void> {
const id = req.query.id as string const id = req.query.id as string
if (id === undefined) { if (id === undefined) {
return badRequest(res, 'Missing post id') badRequest(res, 'Missing post id'); return
} }
const result = await post.fetch(id) const result = await post.fetch(id)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,18 +1,21 @@
import post from '../../services/posts' import post from 'services/posts'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function postFetchLikesController (req: Request, res: Response): Promise<void> { async function postFetchLikesController (
req: Request,
res: Response
): Promise<void> {
const id = req.query.id as string const id = req.query.id as string
if (id === undefined) { if (id === undefined) {
return badRequest(res, 'Missing post id') badRequest(res, 'Missing post id'); return
} }
const result = await post.fetchLikes(id) const result = await post.fetchLikes(id)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,15 +1,18 @@
import post from '../../services/posts' import post from 'services/posts'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function postUpdateController (req: Request, res: Response): Promise<void> { async function postUpdateController (
req: Request,
res: Response
): Promise<void> {
const { postId, content } = req.body const { postId, content } = req.body
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const result = await post.update(postId, content, userId) const result = await post.update(postId, content, userId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Router } from 'express' import { Router } from 'express'
// Controllers // Controllers
import user from './users' import user from './users'
// Middlewares // Middlewares
import authenticated from '../middlewares/authenticated' import authenticated from 'middlewares/authenticated'
import uploadFile from '../middlewares/upload-image' import uploadFile from 'middlewares/upload-image'
const usersRouter = Router() const usersRouter = Router()
@ -24,7 +23,12 @@ usersRouter.post('/like-post', authenticated, user.likePost)
usersRouter.post('/signup', user.signup) usersRouter.post('/signup', user.signup)
// PUT // PUT
usersRouter.put('/profile-picture/upload', authenticated, uploadFile, user.uploadPicture) usersRouter.put(
'/profile-picture/upload',
authenticated,
uploadFile,
user.uploadPicture
)
usersRouter.put('/update', authenticated, user.update) usersRouter.put('/update', authenticated, user.update)
export default usersRouter export default usersRouter

View file

@ -1,14 +1,14 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userAuthController (req: Request, res: Response): Promise<void> { async function userAuthController (req: Request, res: Response): Promise<void> {
const { email, password } = req.body const { email, password } = req.body
const result = await user.auth(email, password) const result = await user.auth({ email, password })
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,13 +1,16 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userDeleteController (req: Request, res: Response): Promise<void> { async function userDeleteController (
req: Request,
res: Response
): Promise<void> {
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const result = await user.delete(userId) const result = await user.delete(userId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,18 +1,21 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userFetchInfoController (req: Request, res: Response): Promise<void> { async function userFetchInfoController (
req: Request,
res: Response
): Promise<void> {
const username = req.query.u as string const username = req.query.u as string
if (username === undefined) { if (username === undefined) {
return badRequest(res, 'Missing username') badRequest(res, 'Missing username'); return
} }
const result = await user.fetchInfo(username.toLowerCase()) const result = await user.fetchInfo(username.toLowerCase())
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,12 +1,15 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userFetchPostsController (req: Request, res: Response): Promise<void> { async function userFetchPostsController (
req: Request,
res: Response
): Promise<void> {
const username = req.query.u as string const username = req.query.u as string
if (username === undefined) { if (username === undefined) {
return badRequest(res, 'Missing username') badRequest(res, 'Missing username'); return
} }
const result = await user.fetchPosts(username) const result = await user.fetchPosts(username)

View file

@ -1,15 +1,18 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userFollowController (req: Request, res: Response): Promise<void> { async function userFollowController (
req: Request,
res: Response
): Promise<void> {
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const { userToFollow } = req.body const { userToFollow } = req.body
const result = await user.follow(userId, userToFollow) const result = await user.follow(userId, userToFollow)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,15 +1,18 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userLikeCommentController (req: Request, res: Response): Promise<void> { async function userLikeCommentController (
req: Request,
res: Response
): Promise<void> {
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const { commentId, postId } = req.body const { commentId } = req.body
const result = await user.likeComment(postId, commentId, userId) const result = await user.likeComment(commentId, userId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,15 +1,18 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userLikePostController (req: Request, res: Response): Promise<void> { async function userLikePostController (
req: Request,
res: Response
): Promise<void> {
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
const { postId } = req.body const { postId } = req.body
const result = await user.likePost(postId, userId) const result = await user.likePost(postId, userId)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,12 +1,15 @@
import user from '../../services/users' import user from 'services/users'
import { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userSearchController (req: Request, res: Response): Promise<void> { async function userSearchController (
req: Request,
res: Response
): Promise<void> {
const username = req.query.u as string const username = req.query.u as string
if (username === undefined) { if (username === undefined) {
return badRequest(res, 'Missing username') badRequest(res, 'Missing username'); return
} }
const result = await user.searchUser(username) const result = await user.searchUser(username)

View file

@ -1,14 +1,17 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userSignupController (req: Request, res: Response): Promise<void> { async function userSignupController (
req: Request,
res: Response
): Promise<void> {
const { username, email, password } = req.body const { username, email, password } = req.body
const result = await user.signup(username, email, password) const result = await user.signup({ username, email, password })
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,15 +1,18 @@
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
async function userUpdateController (req: Request, res: Response): Promise<void> { async function userUpdateController (
req: Request,
res: Response
): Promise<void> {
const { email, displayName, username } = req.body const { email, displayName, username } = req.body
const id = req.user?.id ?? '' const id = req.user?.id ?? ''
const result = await user.update({ id, email, displayName, username }) const result = await user.update({ id, email, displayName, username })
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -1,13 +1,16 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
import user from '../../services/users' import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { badRequest } from '../../lib/http-errors' import { badRequest } from 'lib/http-errors'
let url let url
async function userUploadPictureController (req: Request, res: Response): Promise<void> { async function userUploadPictureController (
req: Request,
res: Response
): Promise<void> {
if (req.file === undefined) { if (req.file === undefined) {
return badRequest(res, 'Expected a JPG or PNG file') badRequest(res, 'Expected a JPG or PNG file'); return
} }
const userId = req.user?.id ?? '' const userId = req.user?.id ?? ''
@ -23,7 +26,7 @@ async function userUploadPictureController (req: Request, res: Response): Promis
const result = await user.uploadPicture(userId, url) const result = await user.uploadPicture(userId, url)
if (result instanceof Error) { if (result instanceof Error) {
return badRequest(res, result.message) badRequest(res, result.message); return
} }
res.json(result) res.json(result)

View file

@ -2,8 +2,8 @@ interface userPayload {
id?: string id?: string
displayName?: string | null displayName?: string | null
username?: string username?: string
password?: string
email?: string email?: string
password?: string
token?: string token?: string
} }

View file

@ -1,17 +1,26 @@
import sharp from 'sharp' import sharp from 'sharp'
import s3 from '../clients/s3-client' import s3 from 'clients/s3-client'
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3' import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
export default async function compressImage (imageName: string): Promise<Error | Object > { export default async function compressImage (
imageName: string,
isProfilePicture: string
): Promise<Error | Record<never, never>> {
// Get file from s3 // Get file from s3
const { Body } = await s3.send(new GetObjectCommand({ const { Body } = await s3.send(
Bucket: process.env.AWS_BUCKET ?? '', new GetObjectCommand({
Key: imageName Bucket: process.env.AWS_BUCKET ?? '',
})) Key: imageName
})
)
const imageBuffer = await Body?.transformToByteArray() const imageBuffer = await Body?.transformToByteArray()
const compressedImageBuffer = await sharp(imageBuffer) const compressedImageBuffer = await sharp(imageBuffer)
.resize(
isProfilePicture === 'true' ? 200 : undefined,
isProfilePicture === 'true' ? 200 : undefined
)
.jpeg({ quality: 65 }) .jpeg({ quality: 65 })
.toBuffer() .toBuffer()

View file

@ -0,0 +1,29 @@
import { type NotificationType } from '@prisma/client'
import prisma from 'clients/prisma-client'
export default async function createNotification (
fromUserId: string,
toUserId: string,
content: string,
type: NotificationType
): Promise<Record<never, never> | Error> {
await prisma.notifications.create({
data: {
type,
fromUserId,
toUserId,
content
},
include: {
fromUser: {
select: {
id: true,
displayName: true,
username: true,
profileImage: true
}
}
}
})
return {}
}

View file

@ -1,6 +1,10 @@
import { Response } from 'express' import { type Response } from 'express'
const sendErrorResponse = (res: Response, status: number, message: string): void => { const sendErrorResponse = (
res: Response,
status: number,
message: string
): void => {
res.status(status).json({ error: message }) res.status(status).json({ error: message })
} }
@ -16,6 +20,9 @@ export const forbidden = (res: Response, message = 'Forbidden'): void => {
sendErrorResponse(res, 403, message) sendErrorResponse(res, 403, message)
} }
export const internalServerError = (res: Response, message = 'Internal Server Error'): void => { export const internalServerError = (
res: Response,
message = 'Internal Server Error'
): void => {
sendErrorResponse(res, 500, message) sendErrorResponse(res, 500, message)
} }

View file

@ -1,29 +1,38 @@
import { verify } from 'jsonwebtoken' import { verify } from 'jsonwebtoken'
import prisma from '../clients/prisma-client' import prisma from 'clients/prisma-client'
import type { Response, Request, NextFunction } from 'express' import type { Response, Request, NextFunction } from 'express'
import jwtPayload from '../interfaces/jwt' import type jwtPayload from 'interfaces/jwt'
import { unauthorized } from '../lib/http-errors' import { unauthorized } from 'lib/http-errors'
async function authenticated (req: Request, res: Response, next: NextFunction): Promise<void> { async function authenticated (
if (req.headers.authorization === undefined || req.headers.authorization.length === 0) { req: Request,
return unauthorized(res, 'Missing token') res: Response,
next: NextFunction
): Promise<void> {
if (
req.headers.authorization === undefined ||
req.headers.authorization.length === 0
) {
unauthorized(res, 'Missing token'); return
} }
const token = req.headers.authorization.split(' ')[1] const token = req.headers.authorization.split(' ')[1]
try { try {
const decoded = await new Promise<jwtPayload | undefined>((resolve, reject) => { const decoded = await new Promise<jwtPayload | undefined>(
verify(token, process.env.JWT_ACCESS_SECRET ?? '', (error, decoded) => { (resolve, reject) => {
if (error != null) { verify(token, process.env.JWT_ACCESS_SECRET ?? '', (error, decoded) => {
reject(error) if (error != null) {
} else { reject(error)
resolve(decoded as jwtPayload) } else {
} resolve(decoded as jwtPayload)
}) }
}) })
}
)
if (decoded == null) { if (decoded == null) {
return unauthorized(res, 'Invalid token') unauthorized(res, 'Invalid token'); return
} }
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
@ -33,12 +42,12 @@ async function authenticated (req: Request, res: Response, next: NextFunction):
}) })
if (user == null) { if (user == null) {
return unauthorized(res, 'User does not exists') unauthorized(res, 'User does not exists'); return
} }
req.user = decoded req.user = decoded
return next() next()
} catch (error) { } catch (error) {
unauthorized(res, `JWT Error: ${(error as Error).message}`) unauthorized(res, `JWT Error: ${(error as Error).message}`)
} }

View file

@ -1,6 +1,6 @@
import rateLimit from 'express-rate-limit' import rateLimit from 'express-rate-limit'
import RedisStore from 'rate-limit-redis' import RedisStore from 'rate-limit-redis'
import redis from '../clients/redis-client' import redis from 'clients/redis-client'
let maxConnections let maxConnections

View file

@ -1,29 +1,30 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { Response, Request, NextFunction } from 'express' import type { Response, Request, NextFunction } from 'express'
import multer from 'multer' import multer from 'multer'
import multerConfig from '../config/multer' import multerConfig from 'config/multer'
import compressImage from '../lib/compress-image' import compressImage from 'lib/compress-image'
import { badRequest } from '../lib/http-errors' import { badRequest } from 'lib/http-errors'
function uploadImage (req: Request, res: Response, next: NextFunction) { function uploadImage (req: Request, res: Response, next: NextFunction) {
const upload = multer(multerConfig).single('image') const upload = multer(multerConfig).single('image')
upload(req, res, async (cb: multer.MulterError | Error | any) => { upload(req, res, async (cb: multer.MulterError | Error | any) => {
if (req.user == null) { if (req.user == null) {
return badRequest(res, 'You must be logged in to upload a profile picture') badRequest(
res,
'You must be logged in to upload a profile picture'
); return
} }
if (cb instanceof multer.MulterError || cb instanceof Error) { if (cb instanceof multer.MulterError || cb instanceof Error) {
return badRequest(res, cb.message) badRequest(res, cb.message); return
} }
if (req.file === undefined) { if (req.file === undefined) {
return badRequest(res, 'Expected file') badRequest(res, 'Expected file'); return
} }
// @ts-expect-error property `key` does not exists in types // @ts-expect-error property `key` does not exists in types
await compressImage(req.file?.key) await compressImage(req.file?.key, req.body.isProfilePicture)
next() next()
}) })

View file

@ -1,6 +1,10 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function commentCreateService (postId: string, content: string, authorId: string): Promise<Object | Error> { async function commentCreateService (
postId: string,
content: string,
authorId: string
): Promise<Record<string, unknown> | Error> {
const post = await prisma.post.findFirst({ const post = await prisma.post.findFirst({
where: { where: {
id: postId id: postId

View file

@ -1,6 +1,9 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function commentDeleteService (commentId: string, authorId: string): Promise<Object | Error> { async function commentDeleteService (
commentId: string,
authorId: string
): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
id: authorId id: authorId

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function commentFetchService (commentId: string): Promise<Object | Error> { async function commentFetchService (commentId: string): Promise<Record<string, unknown> | Error> {
const comment = await prisma.comments.findFirst({ const comment = await prisma.comments.findFirst({
where: { where: {
id: commentId id: commentId

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function commentFetchLikesService (id: string): Promise<Object | Error> { async function commentFetchLikesService (id: string): Promise<unknown | Error> {
const post = await prisma.commentLike.findMany({ const post = await prisma.commentLike.findMany({
where: { where: {
commentId: id commentId: id

View file

@ -1,6 +1,10 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function commentUpdateService (content: string, authorId: string, commentId: string): Promise<Object | Error> { async function commentUpdateService (
content: string,
authorId: string,
commentId: string
): Promise<Record<string, unknown> | Error> {
const comment = await prisma.comments.findFirst({ const comment = await prisma.comments.findFirst({
where: { where: {
id: commentId, id: commentId,

View file

@ -1,10 +1,13 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function postCreateService (content: string, authorId: string): Promise<Object | Error> { async function postCreateService (
content: string,
authorId: string
): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ where: { id: authorId } }) const user = await prisma.user.findFirst({ where: { id: authorId } })
if (user === null) { if (user === null) {
return new Error('This user doesn\'t exists') return new Error("This user doesn't exists")
} }
const post = await prisma.post.create({ const post = await prisma.post.create({

View file

@ -1,13 +1,16 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function postDeleteService (postId: string, userId: string): Promise<Object | Error> { async function postDeleteService (
postId: string,
userId: string
): Promise<Record<string, unknown> | Error> {
const post = await prisma.post.findFirst({ where: { id: postId } }) const post = await prisma.post.findFirst({ where: { id: postId } })
if (post === null) { if (post === null) {
return new Error('Post not found') return new Error('Post not found')
} }
if (await prisma.user.findFirst({ where: { id: userId } }) === null) { if ((await prisma.user.findFirst({ where: { id: userId } })) === null) {
return new Error('User not found') return new Error('User not found')
} }

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function postFetchInfoService (id: string): Promise<Object | Error> { async function postFetchInfoService (id: string): Promise<Record<string, unknown> | Error> {
const post = await prisma.post.findFirst({ const post = await prisma.post.findFirst({
where: { where: {
id id

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function postFetchLikesService (id: string): Promise<Object | Error> { async function postFetchLikesService (id: string): Promise<unknown | Error> {
const post = await prisma.postLike.findMany({ const post = await prisma.postLike.findMany({
where: { where: {
postId: id postId: id

View file

@ -1,13 +1,17 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function postUpdateService (postId: string, content: string, userId: string): Promise<Object | Error> { async function postUpdateService (
postId: string,
content: string,
userId: string
): Promise<Record<string, unknown> | Error> {
const post = await prisma.post.findFirst({ where: { id: postId } }) const post = await prisma.post.findFirst({ where: { id: postId } })
if (post === null) { if (post === null) {
return new Error('Post not found') return new Error('Post not found')
} }
if (await prisma.user.findFirst({ where: { id: userId } }) === null) { if ((await prisma.user.findFirst({ where: { id: userId } })) === null) {
return new Error('User not found') return new Error('User not found')
} }

View file

@ -1,8 +1,12 @@
import * as bcrypt from 'bcrypt' import * as bcrypt from 'bcrypt'
import jsonwebtoken from 'jsonwebtoken' import jsonwebtoken from 'jsonwebtoken'
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
import type userPayload from 'interfaces/user'
async function userAuthService (email: string, password: string): Promise<Object | Error> { async function userAuthService ({
email,
password
}: userPayload): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
email email
@ -17,7 +21,10 @@ async function userAuthService (email: string, password: string): Promise<Object
return new Error('Missing fields') return new Error('Missing fields')
} }
const validPassword = await bcrypt.compare(password.replace(/ /g, ''), user.password) const validPassword = await bcrypt.compare(
password.replace(/ /g, ''),
user.password
)
if (!validPassword) { if (!validPassword) {
return new Error('Invalid email or password') return new Error('Invalid email or password')
@ -25,7 +32,11 @@ async function userAuthService (email: string, password: string): Promise<Object
const { id } = user const { id } = user
const bearer = jsonwebtoken.sign({ id }, process.env.JWT_ACCESS_SECRET ?? '', { expiresIn: '1d' }) const bearer = jsonwebtoken.sign(
{ id },
process.env.JWT_ACCESS_SECRET ?? '',
{ expiresIn: '1d' }
)
return { return {
token: bearer token: bearer

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userDeleteService (userId: string): Promise<Object | Error> { async function userDeleteService (userId: string): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ where: { id: userId } }) const user = await prisma.user.findFirst({ where: { id: userId } })
if (user === null) { if (user === null) {

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userFetchInfoService (username: string): Promise<Object | Error> { async function userFetchInfoService (username: string): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
username username

View file

@ -1,6 +1,8 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userFetchPostsService (username: string): Promise<Object | Error> { async function userFetchPostsService (
username: string
): Promise<unknown | Error> {
const posts = await prisma.post.findMany({ const posts = await prisma.post.findMany({
where: { where: {
author: { author: {

View file

@ -1,6 +1,9 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userFollowService (userId: string, followingUsername: string): Promise<Object | Error> { async function userFollowService (
userId: string,
followingUsername: string
): Promise<Record<string, unknown> | Error> {
if (userId === undefined || followingUsername === undefined) { if (userId === undefined || followingUsername === undefined) {
return new Error('Missing fields') return new Error('Missing fields')
} }

View file

@ -1,6 +1,9 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userLikeCommentService (postId: string, commentId: string, userId: string): Promise<Object | Error> { async function userLikeCommentService (
commentId: string,
userId: string
): Promise<Record<string, unknown> | Error> {
if (commentId === undefined || userId === undefined) { if (commentId === undefined || userId === undefined) {
return new Error('Missing fields') return new Error('Missing fields')
} }

View file

@ -1,6 +1,9 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userLikePostService (postId: string, userId: string): Promise<Object | Error> { async function userLikePostService (
postId: string,
userId: string
): Promise<Record<string, unknown> | Error> {
if (postId === undefined || userId === undefined) { if (postId === undefined || userId === undefined) {
return new Error('Missing fields') return new Error('Missing fields')
} }

View file

@ -1,6 +1,6 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userSearchService (username: string): Promise<Object | Error> { async function userSearchService (username: string): Promise<unknown | Error> {
const users = await prisma.user.findMany({ const users = await prisma.user.findMany({
where: { where: {
username: { username: {

View file

@ -1,17 +1,24 @@
import * as bcrypt from 'bcrypt' import * as bcrypt from 'bcrypt'
import validator from 'validator' import validator from 'validator'
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
import type userPayload from 'interfaces/user'
const passwordRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*_])[a-zA-Z0-9!@#$%^&*_]{8,}$/ const passwordRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*_])[a-zA-Z0-9!@#$%^&*_]{8,}$/
const usernameRegex = /^[a-zA-Z0-9_.]{5,15}$/ const usernameRegex = /^[a-zA-Z0-9_.]{5,15}$/
async function userSignupService (username: string, email: string, password: string): Promise<Object | Error> { async function userSignupService ({
username,
email,
password
}: userPayload): Promise<Record<string, unknown> | Error> {
if (username === undefined || email === undefined || password === undefined) { if (username === undefined || email === undefined || password === undefined) {
return new Error('Missing fields') return new Error('Missing fields')
} }
if (!passwordRegex.test(password)) { if (!passwordRegex.test(password)) {
return new Error('Password must have at least 8 characters, one number and one special character.') return new Error(
'Password must have at least 8 characters, one number and one special character.'
)
} }
if (password.trim().length < 8) { if (password.trim().length < 8) {
@ -19,7 +26,9 @@ async function userSignupService (username: string, email: string, password: str
} }
if (!usernameRegex.test(username)) { 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') 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)) { if (!validator.isEmail(email)) {

View file

@ -1,7 +1,12 @@
import userPayload from '../../interfaces/user' import type userPayload from 'interfaces/user'
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userUpdateService ({ id, email, displayName, username }: userPayload): Promise<Object | Error> { async function userUpdateService ({
id,
email,
displayName,
username
}: userPayload): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ where: { id } }) const user = await prisma.user.findFirst({ where: { id } })
if (user === null) { if (user === null) {
@ -13,14 +18,14 @@ async function userUpdateService ({ id, email, displayName, username }: userPayl
if (email !== undefined && email.trim() !== user.email) { if (email !== undefined && email.trim() !== user.email) {
const existingUser = await prisma.user.findFirst({ where: { email } }) const existingUser = await prisma.user.findFirst({ where: { email } })
if ((existingUser != null) && existingUser.email !== user.email) { if (existingUser != null && existingUser.email !== user.email) {
return new Error('Email already in use') return new Error('Email already in use')
} }
} }
if (username !== undefined && username.trim() !== user.username) { if (username !== undefined && username.trim() !== user.username) {
const existingUser = await prisma.user.findFirst({ where: { username } }) const existingUser = await prisma.user.findFirst({ where: { username } })
if ((existingUser != null) && existingUser.username !== user.username) { if (existingUser != null && existingUser.username !== user.username) {
return new Error('Username already in use') return new Error('Username already in use')
} }
} }

View file

@ -1,6 +1,9 @@
import prisma from '../../clients/prisma-client' import prisma from 'clients/prisma-client'
async function userUploadPictureService (authorId: string, url: string): Promise<Object | Error> { async function userUploadPictureService (
authorId: string,
url: string
): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ where: { id: authorId } }) const user = await prisma.user.findFirst({ where: { id: authorId } })
if (user == null) { if (user == null) {

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -16,17 +16,23 @@ describe('POST /post/create', () => {
}) })
it('should respond with 200 status code if the user send the token and the content', async () => { 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({ const response = await request(app)
content: 'Hello world' .post('/post/create')
}).set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .send({
content: 'Hello world'
})
.set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
expect(response.body).toEqual(expect.objectContaining({ expect(response.body).toEqual(
id: expect.any(String), expect.objectContaining({
content: expect.any(String), id: expect.any(String),
authorId: expect.any(String), content: expect.any(String),
createdAt: expect.any(String), authorId: expect.any(String),
updatedAt: 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 () => { it('should respond with 400 status code if the user send no token', async () => {

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -21,13 +21,15 @@ describe('DELETE /post/delete', () => {
.send({ .send({
content: 'lorem ipsum' content: 'lorem ipsum'
}) })
.set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
await request(app) await request(app)
.post('/post/delete') .post('/post/delete')
.send({ .send({
postId: response.body.id postId: response.body.id
}) })
.set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
}) })
}) })

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let postId: string let postId: string
@ -14,9 +14,13 @@ describe('POST /post/info', () => {
const token = user.token ?? '' const token = user.token ?? ''
const post = await request(app).post('/post/create').send({ const post = await request(app)
content: 'Hello world' .post('/post/create')
}).set('Authorization', `Bearer ${token}`).expect(200) .send({
content: 'Hello world'
})
.set('Authorization', `Bearer ${token}`)
.expect(200)
postId = post.body.id postId = post.body.id
}) })
@ -26,15 +30,19 @@ describe('POST /post/info', () => {
}) })
it('should respond with 200 status code and return some info about the post', async () => { 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) const response = await request(app)
.get(`/post/info?id=${postId}`)
.expect(200)
expect(response.body).toEqual(expect.objectContaining({ expect(response.body).toEqual(
id: expect.any(String), expect.objectContaining({
content: expect.any(String), id: expect.any(String),
createdAt: expect.any(String), content: expect.any(String),
updatedAt: expect.any(String), createdAt: expect.any(String),
author: expect.any(Object) updatedAt: expect.any(String),
})) author: expect.any(Object)
})
)
}) })
it('should respond with 400 status code if the post does not exists', async () => { it('should respond with 400 status code if the post does not exists', async () => {

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -16,9 +16,13 @@ describe('PUT /post/update', () => {
}) })
it('should create a new post and update the content of it', async () => { it('should create a new post and update the content of it', async () => {
const post = await request(app).post('/post/create').send({ const post = await request(app)
content: 'Lorem' .post('/post/create')
}).set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .send({
content: 'Lorem'
})
.set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
expect(post.body).toHaveProperty('id') expect(post.body).toHaveProperty('id')
@ -30,19 +34,22 @@ describe('PUT /post/update', () => {
const response = await request(app) const response = await request(app)
.put('/post/update') .put('/post/update')
.send(fieldsToUpdate) .send(fieldsToUpdate)
.set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
// Post content should be Lorem Ipsum // Post content should be Lorem Ipsum
if (post.body.content === response.body.content) { if (post.body.content === response.body.content) {
throw new Error('Post didn\'t update') throw new Error("Post didn't update")
} }
expect(response.body).toEqual(expect.objectContaining({ expect(response.body).toEqual(
id: expect.any(String), expect.objectContaining({
content: expect.any(String), id: expect.any(String),
createdAt: expect.any(String), content: expect.any(String),
updatedAt: expect.any(String), createdAt: expect.any(String),
author: expect.any(Object) updatedAt: expect.any(String),
})) author: expect.any(Object)
})
)
}) })
}) })

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -16,14 +16,20 @@ describe('POST /user/auth', () => {
}) })
it('should respond with a error if the user does not exists', async () => { 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) 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).toHaveProperty('error')
expect(response.body.error).toBe('User does not exists') expect(response.body.error).toBe('User does not exists')
}) })
it('should respond with a error if receive an invalid email or password', async () => { 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) 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).toHaveProperty('error')
expect(response.body.error).toBe('Invalid email or password') expect(response.body.error).toBe('Invalid email or password')

View file

@ -1,7 +1,7 @@
import app from '../../app' import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -11,7 +11,8 @@ describe('DELETE /user/delete', () => {
}) })
it('should delete the user successfully', async () => { it('should delete the user successfully', async () => {
await request(app).post('/user/delete') await request(app)
.post('/user/delete')
.set('Authorization', `Bearer ${user.token ?? ''}`) .set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200) .expect(200)
}) })

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -16,7 +16,9 @@ describe('POST /user/info', () => {
}) })
it('should respond with 200 status code and return the user data', async () => { 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) const response = await request(app)
.get(`/user/info?u=${user.username ?? ''}`)
.expect(200)
expect(response.body).toHaveProperty('profileImage') expect(response.body).toHaveProperty('profileImage')
expect(response.body).toHaveProperty('displayName') expect(response.body).toHaveProperty('displayName')

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -17,19 +17,25 @@ describe('POST /user/signup', () => {
}) })
it('should respond with a 400 status code if sent any invalid data', async () => { it('should respond with a 400 status code if sent any invalid data', async () => {
await request(app).post('/user/signup').send({ await request(app)
username: 'username12@', .post('/user/signup')
email: user.email, .send({
password: user.password username: 'username12@',
}).expect(400) email: user.email,
password: user.password
})
.expect(400)
}) })
it('should respond with a 400 status code for an existing username or email', async () => { it('should respond with a 400 status code for an existing username or email', async () => {
await request(app).post('/user/signup').send({ await request(app)
username: user.username, .post('/user/signup')
email: user.email, .send({
password: user.password username: user.username,
}).expect(400) email: user.email,
password: user.password
})
.expect(400)
}) })
it('should respond with a 400 status code if receive an empty body', async () => { it('should respond with a 400 status code if receive an empty body', async () => {

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
let user: userPayload let user: userPayload
@ -23,12 +23,15 @@ describe('PUT /user/update', () => {
const response = await request(app) const response = await request(app)
.put('/user/update') .put('/user/update')
.send(fieldsToUpdate) .send(fieldsToUpdate)
.set('Authorization', `Bearer ${user.token ?? ''}`).expect(200) .set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
expect(response.body).toEqual(expect.objectContaining({ expect(response.body).toEqual(
displayName: expect.any(String), expect.objectContaining({
username: expect.any(String), displayName: expect.any(String),
createdAt: expect.any(String) username: expect.any(String),
})) createdAt: expect.any(String)
})
)
}) })
}) })

View file

@ -1,7 +1,7 @@
import app from '../../app' import app from '../../app'
import request from 'supertest' import request from 'supertest'
import { faker } from '@faker-js/faker' import { faker } from '@faker-js/faker'
import userPayload from '../../interfaces/user' import type userPayload from '../../interfaces/user'
async function signUpNewUser (): Promise<userPayload> { async function signUpNewUser (): Promise<userPayload> {
// To avoid conflicts with existing usernames or emails // To avoid conflicts with existing usernames or emails
@ -9,18 +9,22 @@ async function signUpNewUser (): Promise<userPayload> {
const email = faker.internet.email() const email = faker.internet.email()
const password = faker.internet.password() + '@1' const password = faker.internet.password() + '@1'
await request(app).post('/user/signup').send({ await request(app)
username, .post('/user/signup')
email, .send({
password username,
}).expect(200) email,
password
})
.expect(200)
const response = await request(app) const response = await request(app)
.post('/user/auth') .post('/user/auth')
.send({ .send({
email, email,
password password
}).expect(200) })
.expect(200)
return { return {
username, username,

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import prisma from '../../clients/prisma-client' import prisma from '../../clients/prisma-client'
export default async function deleteUser (username: string) { export default async function deleteUser (username: string) {

View file

@ -1,105 +1,47 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "target": "es2016",
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */ /* Modules */
"module": "commonjs", /* Specify what module code is generated. */ "module": "commonjs",
"rootDir": "./", /* Specify the root folder within your source files. */ "rootDir": "./",
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ "baseUrl": "./src",
"baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ "paths": {
/* Specify a set of entries that re-map imports to additional lookup locations. */ "clients/*": [
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ "clients/*"
],
"config/*": [
"config/*"
],
"controllers/*": [
"controllers/*"
],
"interfaces/*": [
"interfaces/*"
],
"lib/*": [
"lib/*"
],
"middlewares/*": [
"middlewares/*"
],
"services/*": [
"services/*"
]
},
"typeRoots": [ "typeRoots": [
"src/@types/express.d.ts", "src/@types/express.d.ts",
"src/@types/global.d.ts", "src/@types/global.d.ts",
"node_modules/@types" "node_modules/@types"
], /* Specify multiple folders that act like './node_modules/@types'. */ ],
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */ /* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ "outDir": "./dist",
// "declarationMap": true, /* Create sourcemaps for d.ts files. */ "removeComments": true,
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */ /* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "esModuleInterop": true,
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ "forceConsistentCasingInFileNames": true,
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */ /* Type Checking */
"strict": true, /* Enable all strict type-checking options. */ "strict": true,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ "skipLibCheck": true
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
} }
} }