diff --git a/.swcrc b/.swcrc index eeada9d..377811f 100644 --- a/.swcrc +++ b/.swcrc @@ -10,7 +10,6 @@ "baseUrl": "./" }, "exclude": [ - "tests/", "@types/", "interfaces/" ], diff --git a/README.md b/README.md index 7b2b6cc..4c84322 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A simple social media created with React Native and Express. ## To-do - Backend + - Create/update/delete Posts - Password recuperation - Two step verification @@ -12,8 +13,9 @@ A simple social media created with React Native and Express. - Like posts - Probably pinned posts - Authentication ✅ - - Add more verification (like, if the password is too short) + - Add more verification (like, if the password is too short) +- Set display name + ## License [MIT](https://choosealicense.com/licenses/mit/) - diff --git a/src/controllers/posts-router.ts b/src/controllers/posts-router.ts new file mode 100644 index 0000000..4f45db8 --- /dev/null +++ b/src/controllers/posts-router.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ + +import { Router } from 'express' + +// Controllers +import postCreateController from './posts/post-create' +import postDeleteController from './posts/post-delete' +import postInfoController from './posts/post-info' +import postUpdateController from './posts/post-update' + +// Middlewares +import ensureAuthenticated from '../middlewares/ensure-authenticated' + +const postsRouter = Router() + +// Posts related +postsRouter.post('/create', ensureAuthenticated, postCreateController) +postsRouter.post('/delete', ensureAuthenticated, postDeleteController) +postsRouter.get('/info', postInfoController) +postsRouter.put('/update', postUpdateController) + +export default postsRouter diff --git a/src/controllers/post/create-post.ts b/src/controllers/posts/post-create.ts similarity index 55% rename from src/controllers/post/create-post.ts rename to src/controllers/posts/post-create.ts index 34764ef..868c015 100644 --- a/src/controllers/post/create-post.ts +++ b/src/controllers/posts/post-create.ts @@ -1,11 +1,11 @@ -import createPostService from '../../services/post/create-post' +import { posts } from '../../services/index' import { Request, Response } from 'express' -async function createPostController (req: Request, res: Response): Promise { +async function postCreateController (req: Request, res: Response): Promise { const { content } = req.body const id: string = req.user?.id ?? '' - const result = await createPostService(content, id) + const result = await posts.postCreate(content, id) if (result instanceof Error) { res.status(400).json({ @@ -17,4 +17,4 @@ async function createPostController (req: Request, res: Response): Promise res.json(result) } -export default createPostController +export default postCreateController diff --git a/src/controllers/posts/post-delete.ts b/src/controllers/posts/post-delete.ts new file mode 100644 index 0000000..f4dfc3a --- /dev/null +++ b/src/controllers/posts/post-delete.ts @@ -0,0 +1,20 @@ +import { posts } from '../../services/index' +import { Request, Response } from 'express' + +async function postDeleteController (req: Request, res: Response): Promise { + const userId = req.user?.id ?? '' + const postId = req.body.postId + + const result = await posts.postDelete(postId, userId) + + if (result instanceof Error) { + res.status(400).json({ + error: result.message + }) + return + } + + res.json(result) +} + +export default postDeleteController diff --git a/src/controllers/posts/post-info.ts b/src/controllers/posts/post-info.ts new file mode 100644 index 0000000..dbccd21 --- /dev/null +++ b/src/controllers/posts/post-info.ts @@ -0,0 +1,26 @@ +import { posts } from '../../services/index' +import { Request, Response } from 'express' + +async function postInfoController (req: Request, res: Response): Promise { + const id = req.query.id as string + + if (id === undefined) { + res.status(400).json({ + error: 'Missing username' + }) + return + } + + const result = await posts.postInfo(id) + + if (result instanceof Error) { + res.status(400).json({ + error: result.message + }) + return + } + + res.json(result) +} + +export default postInfoController diff --git a/src/controllers/posts/post-update.ts b/src/controllers/posts/post-update.ts new file mode 100644 index 0000000..b861ff5 --- /dev/null +++ b/src/controllers/posts/post-update.ts @@ -0,0 +1,17 @@ +import { posts } from '../../services/index' +import { Request, Response } from 'express' + +async function postUpdateController (req: Request, res: Response): Promise { + const result = await posts.postUpdate() + + if (result instanceof Error) { + res.status(400).json({ + error: result.message + }) + return + } + + res.json(result) +} + +export default postUpdateController diff --git a/src/controllers/users-router.ts b/src/controllers/users-router.ts new file mode 100644 index 0000000..b2f700e --- /dev/null +++ b/src/controllers/users-router.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ + +import { Router } from 'express' + +// Controllers +import userAuthController from './users/user-auth' +import userDeleteController from './users/user-delete' +import userInfoController from './users/user-info' +import userSignupController from './users/user-signup' +import userUpdateController from './users/user-update' + +// Middlewares +import ensureAuthenticated from '../middlewares/ensure-authenticated' + +const usersRouter = Router() + +// Users related +usersRouter.post('/auth', userAuthController) +usersRouter.post('/delete', ensureAuthenticated, userDeleteController) +usersRouter.get('/info', userInfoController) +usersRouter.post('/signup', userSignupController) +usersRouter.put('/update', ensureAuthenticated, userUpdateController) + +export default usersRouter diff --git a/src/controllers/user/user-auth.ts b/src/controllers/users/user-auth.ts similarity index 74% rename from src/controllers/user/user-auth.ts rename to src/controllers/users/user-auth.ts index bc40ce2..616669b 100644 --- a/src/controllers/user/user-auth.ts +++ b/src/controllers/users/user-auth.ts @@ -1,10 +1,10 @@ -import userAuthService from '../../services/user/user-auth' +import { users } from '../../services/index' import type { Request, Response } from 'express' async function userAuthController (req: Request, res: Response): Promise { const { email, password } = req.body - const result = await userAuthService(email, password) + const result = await users.userAuth(email, password) if (result instanceof Error) { res.status(400).json({ diff --git a/src/controllers/users/user-delete.ts b/src/controllers/users/user-delete.ts new file mode 100644 index 0000000..6165acf --- /dev/null +++ b/src/controllers/users/user-delete.ts @@ -0,0 +1,18 @@ +import { users } from '../../services' +import { Request, Response } from 'express' + +async function userDeleteController (req: Request, res: Response): Promise { + const userId = req.user?.id ?? '' + const result = await users.userDelete(userId) + + if (result instanceof Error) { + res.status(400).json({ + error: result.message + }) + return + } + + res.json(result) +} + +export default userDeleteController diff --git a/src/controllers/user/user-info.ts b/src/controllers/users/user-info.ts similarity index 53% rename from src/controllers/user/user-info.ts rename to src/controllers/users/user-info.ts index a33a7b9..2644e39 100644 --- a/src/controllers/user/user-info.ts +++ b/src/controllers/users/user-info.ts @@ -1,10 +1,17 @@ -import userInfoService from '../../services/user/user-info' +import { users } from '../../services' import type { Request, Response } from 'express' async function userInfoController (req: Request, res: Response): Promise { - const id = req.user?.id ?? '' + const username = req.query.u as string - const result = await userInfoService(id) + if (username === undefined) { + res.status(400).json({ + error: 'Missing username' + }) + return + } + + const result = await users.userInfo(username.toLowerCase()) if (result instanceof Error) { res.status(400).json({ diff --git a/src/controllers/user/user-signup.ts b/src/controllers/users/user-signup.ts similarity index 72% rename from src/controllers/user/user-signup.ts rename to src/controllers/users/user-signup.ts index 64352b4..d1a0303 100644 --- a/src/controllers/user/user-signup.ts +++ b/src/controllers/users/user-signup.ts @@ -1,10 +1,10 @@ -import userSignupService from '../../services/user/user-signup' +import { users } from '../../services' import type { Request, Response } from 'express' async function userSignupController (req: Request, res: Response): Promise { const { username, email, password } = req.body - const result = await userSignupService(username, email, password) + const result = await users.userSignup(username, email, password) if (result instanceof Error) { res.status(400).json({ diff --git a/src/controllers/users/user-update.ts b/src/controllers/users/user-update.ts new file mode 100644 index 0000000..8ac5b61 --- /dev/null +++ b/src/controllers/users/user-update.ts @@ -0,0 +1,17 @@ +import { users } from '../../services' +import { Request, Response } from 'express' + +async function userUpdateController (req: Request, res: Response): Promise { + const result = await users.userUpdate() + + if (result instanceof Error) { + res.status(400).json({ + error: result.message + }) + return + } + + res.json(result) +} + +export default userUpdateController diff --git a/src/routes.ts b/src/routes.ts index df377fb..1ea81e4 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,22 +1,12 @@ import { Router } from 'express' -// Controllers -import userSignupController from './controllers/user/user-signup' -import userAuthController from './controllers/user/user-auth' -import userInfoController from './controllers/user/user-info' -import createPostController from './controllers/post/create-post' - -// Middlewares -import ensureAuthenticated from './middlewares/ensure-authenticated' +// Routers +import usersRouter from './controllers/users-router' +import postsRouter from './controllers/posts-router' const router = Router() -// User related -router.post('/user/auth', userAuthController) -router.post('/user/create', userSignupController) -router.get('/user/info', ensureAuthenticated, userInfoController) - -// Post related -router.post('/post/create', ensureAuthenticated, createPostController) +router.use('/user', usersRouter) +router.use('/post', postsRouter) export default router diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..2696938 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,29 @@ +import userAuthService from './users/user-auth' +import userDeleteService from './users/user-delete' +import userInfoService from './users/user-info' +import userSignupService from './users/user-signup' +import userUpdateService from './users/user-update' + +import postCreateService from './posts/post-create' +import postDeleteService from './posts/post-delete' +import postInfoService from './posts/post-info' +import postUpdateService from './posts/post-update' + +// User services +const users = { + userAuth: userAuthService, + userDelete: userDeleteService, + userInfo: userInfoService, + userSignup: userSignupService, + userUpdate: userUpdateService +} + +// Post services +const posts = { + postCreate: postCreateService, + postDelete: postDeleteService, + postInfo: postInfoService, + postUpdate: postUpdateService +} + +export { users, posts } diff --git a/src/services/post/create-post.ts b/src/services/posts/post-create.ts similarity index 77% rename from src/services/post/create-post.ts rename to src/services/posts/post-create.ts index b00ea93..0930b87 100644 --- a/src/services/post/create-post.ts +++ b/src/services/posts/post-create.ts @@ -1,6 +1,6 @@ import prisma from '../../db' -async function createPostService (content: string, authorId: string): Promise { +async function postCreateService (content: string, authorId: string): Promise { const user = await prisma.user.findFirst({ where: { id: authorId } }) if (user === null) { @@ -17,4 +17,4 @@ async function createPostService (content: string, authorId: string): Promise { + const post = await prisma.post.findFirst({ where: { id: postId } }) + + console.log(postId, userId) + + 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 diff --git a/src/services/posts/post-info.ts b/src/services/posts/post-info.ts new file mode 100644 index 0000000..0853b0d --- /dev/null +++ b/src/services/posts/post-info.ts @@ -0,0 +1,29 @@ +import prisma from '../../db' + +async function postInfoService (id: string): Promise { + const post = await prisma.post.findFirst({ + where: { + id + }, + select: { + id: true, + content: true, + createdAt: true, + updatedAt: true, + author: { + select: { + displayName: true, + username: true + } + } + } + }) + + if (post === null) { + return new Error('Post not found') + } + + return post +} + +export default postInfoService diff --git a/src/services/posts/post-update.ts b/src/services/posts/post-update.ts new file mode 100644 index 0000000..fc60be7 --- /dev/null +++ b/src/services/posts/post-update.ts @@ -0,0 +1,6 @@ +import prisma from '../../db' + +async function postUpdateService (): Promise { + return {} +} +export default postUpdateService diff --git a/src/services/user/user-auth.ts b/src/services/users/user-auth.ts similarity index 100% rename from src/services/user/user-auth.ts rename to src/services/users/user-auth.ts diff --git a/src/services/users/user-delete.ts b/src/services/users/user-delete.ts new file mode 100644 index 0000000..ea249d7 --- /dev/null +++ b/src/services/users/user-delete.ts @@ -0,0 +1,29 @@ +import prisma from '../../db' + +async function userDeleteService (userId: string): Promise { + 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.post.deleteMany({ + where: { + authorId: user.id + } + }) + + await prisma.user.deleteMany({ + where: { + id: userId + } + }) + + return {} +} + +export default userDeleteService diff --git a/src/services/user/user-info.ts b/src/services/users/user-info.ts similarity index 52% rename from src/services/user/user-info.ts rename to src/services/users/user-info.ts index 54859b0..69ac4d5 100644 --- a/src/services/user/user-info.ts +++ b/src/services/users/user-info.ts @@ -1,15 +1,22 @@ import prisma from '../../db' -async function userInfoService (id: string): Promise { +async function userInfoService (username: string): Promise { const user = await prisma.user.findFirst({ where: { - id + username }, select: { - id: true, displayName: true, username: true, - createdAt: true + createdAt: true, + posts: { + select: { + id: true, + content: true, + createdAt: true, + updatedAt: true + } + } } }) diff --git a/src/services/user/user-signup.ts b/src/services/users/user-signup.ts similarity index 96% rename from src/services/user/user-signup.ts rename to src/services/users/user-signup.ts index fcfbfd8..bfd4115 100644 --- a/src/services/user/user-signup.ts +++ b/src/services/users/user-signup.ts @@ -28,7 +28,7 @@ async function userSignupService (username: string, email: string, password: str const user = await prisma.user.create({ data: { - username, + username: username.toLowerCase(), email, password: hashedPassword }, diff --git a/src/services/users/user-update.ts b/src/services/users/user-update.ts new file mode 100644 index 0000000..64cd34e --- /dev/null +++ b/src/services/users/user-update.ts @@ -0,0 +1,7 @@ +import prisma from '../../db' + +async function userUpdateService (): Promise { + return {} +} + +export default userUpdateService diff --git a/src/tests/post/create-post.spec.ts b/src/tests/post/post-create.spec.ts similarity index 96% rename from src/tests/post/create-post.spec.ts rename to src/tests/post/post-create.spec.ts index c11f4c7..75970e5 100644 --- a/src/tests/post/create-post.spec.ts +++ b/src/tests/post/post-create.spec.ts @@ -6,7 +6,7 @@ let token = '' describe('POST /post/create', () => { beforeAll(async () => { - await request(app).post('/user/create').send({ + await request(app).post('/user/signup').send({ username: 'dummmyuser7', email: 'random1@email.com', password: 'pass' diff --git a/src/tests/post/post-delete.spec.ts b/src/tests/post/post-delete.spec.ts new file mode 100644 index 0000000..94f4ae3 --- /dev/null +++ b/src/tests/post/post-delete.spec.ts @@ -0,0 +1,9 @@ +import prisma from '../../db' +import app from '../../app' +import request from 'supertest' + +describe('DELETE /post/delete', () => { + test('should ignore', () => { + expect(1 + 1).toBe(2) + }) +}) diff --git a/src/tests/post/post-info.spec.ts b/src/tests/post/post-info.spec.ts new file mode 100644 index 0000000..565b9a8 --- /dev/null +++ b/src/tests/post/post-info.spec.ts @@ -0,0 +1,9 @@ +import prisma from '../../db' +import app from '../../app' +import request from 'supertest' + +describe('POST /post/info', () => { + test('should ignore', () => { + expect(1 + 1).toBe(2) + }) +}) diff --git a/src/tests/post/post-update.spec.ts b/src/tests/post/post-update.spec.ts new file mode 100644 index 0000000..3dc05cc --- /dev/null +++ b/src/tests/post/post-update.spec.ts @@ -0,0 +1,9 @@ +import prisma from '../../db' +import app from '../../app' +import request from 'supertest' + +describe('PUT /post/update', () => { + test('should ignore', () => { + expect(1 + 1).toBe(2) + }) +}) diff --git a/src/tests/user/user-delete.spec.ts b/src/tests/user/user-delete.spec.ts new file mode 100644 index 0000000..e8e8cb8 --- /dev/null +++ b/src/tests/user/user-delete.spec.ts @@ -0,0 +1,9 @@ +import prisma from '../../db' +import app from '../../app' +import request from 'supertest' + +describe('DELETE /user/delete', () => { + test('should ignore', () => { + expect(1 + 1).toBe(2) + }) +}) diff --git a/src/tests/user/user-info.spec.ts b/src/tests/user/user-info.spec.ts index 17a887e..6852eb9 100644 --- a/src/tests/user/user-info.spec.ts +++ b/src/tests/user/user-info.spec.ts @@ -2,24 +2,7 @@ import prisma from '../../db' import app from '../../app' import request from 'supertest' -let token = '' - describe('POST /user/info', () => { - beforeAll(async () => { - await request(app).post('/user/create').send({ - username: 'dummmyuser5', - email: 'random3@email.com', - password: 'pass' - }) - - const response = await request(app).post('/user/auth').send({ - email: 'random3@email.com', - password: 'pass' - }).expect(200) - - token = response.body.token - }) - afterAll(async () => { await prisma.user.deleteMany({ where: { @@ -30,16 +13,24 @@ describe('POST /user/info', () => { }) it('should respond with 200 status code and return the user data', async () => { - const response = await request(app).get('/user/info').set('Authorization', `Bearer ${token}`).expect(200) + await prisma.user.create({ + data: { + username: 'dummmyuser5', + email: 'random3@email.com', + password: 'pass' + } + }) + + const response = await request(app).get('/user/info?u=dummmyuser5').expect(200) - expect(response.body).toHaveProperty('id') expect(response.body).toHaveProperty('displayName') expect(response.body).toHaveProperty('username') expect(response.body).toHaveProperty('createdAt') + expect(response.body).toHaveProperty('posts') }) - it('should respond with 400 status code if the user send no token', async () => { - const response = await request(app).get('/user/info').expect(401) + 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') }) diff --git a/src/tests/user/user-signup.spec.ts b/src/tests/user/user-signup.spec.ts index 0069c84..8fe23f6 100644 --- a/src/tests/user/user-signup.spec.ts +++ b/src/tests/user/user-signup.spec.ts @@ -8,9 +8,9 @@ const mockUser = { password: 'totallysafepass' } -describe('POST /user/create', () => { +describe('POST /user/signup', () => { it('should respond with a 200 status code', async () => { - const response = await request(app).post('/user/create').send(mockUser).expect(200) + const response = await request(app).post('/user/signup').send(mockUser).expect(200) expect(response.body).toHaveProperty('displayName') expect(response.body).toHaveProperty('username') @@ -18,7 +18,7 @@ describe('POST /user/create', () => { }) it('should respond with a 400 status code if sent any invalid data', async () => { - await request(app).post('/user/create').send({ + await request(app).post('/user/signup').send({ username: 'username12@', email: mockUser.email, password: mockUser.password @@ -34,7 +34,7 @@ describe('POST /user/create', () => { } }) - const response = await request(app).post('/user/create').send({ + const response = await request(app).post('/user/signup').send({ username: 'dummmyuser2', email: 'user1@email.com', password: 'reallystrongpass' @@ -52,7 +52,7 @@ describe('POST /user/create', () => { } }) - const response = await request(app).post('/user/create').send({ + const response = await request(app).post('/user/signup').send({ username: 'dummmyuser4', email: 'user13@email.com', password: '12345' @@ -62,7 +62,7 @@ describe('POST /user/create', () => { }) it('should respond with a 400 status code if receive an empty body', async () => { - const response = await request(app).post('/user/create').send({}).expect(400) + const response = await request(app).post('/user/signup').send({}).expect(400) expect(response.body).toHaveProperty('error') }) diff --git a/src/tests/user/user-update.spec.ts b/src/tests/user/user-update.spec.ts new file mode 100644 index 0000000..3670aa5 --- /dev/null +++ b/src/tests/user/user-update.spec.ts @@ -0,0 +1,9 @@ +import prisma from '../../db' +import app from '../../app' +import request from 'supertest' + +describe('PUT /user/update', () => { + test('should ignore', () => { + expect(1 + 1).toBe(2) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 9c6c5bb..c5e81ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,14 +26,7 @@ "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ - "paths": { - "@services/*": [ - "services/*" - ], - "@db/*": [ - "../prisma/*" - ] - }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ "typeRoots": [ "src/@types",