From 1be333d526ca5cde2a889a6af42fb804bb52c055 Mon Sep 17 00:00:00 2001 From: masterchief-Dave Date: Wed, 24 Jul 2024 21:19:16 +0100 Subject: [PATCH 1/2] feat: create documentation for user routes --- src/controllers/ProductController.ts | 2 + src/controllers/UserController.ts | 341 +++++++++++++++++++++++++-- src/routes/sms.ts | 4 +- src/schema/product.schema.ts | 17 +- src/schema/user.schema.ts | 137 +++++++++++ src/services/auth.services.ts | 16 +- 6 files changed, 487 insertions(+), 30 deletions(-) create mode 100644 src/schema/user.schema.ts diff --git a/src/controllers/ProductController.ts b/src/controllers/ProductController.ts index 777065e4..245c02f5 100644 --- a/src/controllers/ProductController.ts +++ b/src/controllers/ProductController.ts @@ -342,6 +342,8 @@ export class ProductController { * message: * type: string * example: "Valid product ID, name, description, price, and stock must be provided." + * 401: + * description: Unauthorized * 500: * description: Server error * content: diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index c85c06b1..74e26de8 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -1,17 +1,69 @@ // src/controllers/UserController.ts -import { Request, Response, NextFunction } from "express"; -import { UserService } from "../services"; -import { HttpError } from "../middleware"; import { isUUID } from "class-validator"; +import { NextFunction, Request, Response } from "express"; import { validate } from "uuid"; +import { HttpError } from "../middleware"; +import { UserService } from "../services"; class UserController { private userService: UserService; + /** + * @swagger + * tags: + * name: User + * description: User related routes + */ constructor() { this.userService = new UserService(); } + /** + * @swagger + * /api/v1/users/me: + * get: + * tags: + * - User + * summary: Get User profile + * security: + * - bearerAuth: [] + * description: Api endpoint to retrieve the profile data of the currently authenticated user. This will allow users to access their own profile information. + * responses: + * 200: + * description: Fetched User profile Successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * status_code: + * type: number + * example: 200 + * data: + * type: object + * properties: + * id: + * type: string + * example: 58b6 + * user_name: + * type: string + * example: yasuke + * email: + * type: string + * example: sam@gmail.com + * profile_picture: + * type: string + * example: https://avatar.com + * + * 401: + * description: Unauthorized access + * 404: + * description: Not found + * 500: + * description: Internal Server Error + * + */ + static async getProfile(req: Request, res: Response, next: NextFunction) { try { const { id } = req.user; @@ -67,6 +119,61 @@ class UserController { } } + /** + * @swagger + * /api/v1/users: + * get: + * tags: + * - User + * summary: Get all users + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: Get all users + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: success + * status_code: + * type: integer + * example: 200 + * message: + * type: string + * example: Users retrieved successfully + * pagination: + * type: object + * properties: + * totalItems: + * type: integer + * example: 100 + * totalPages: + * type: integer + * example: 10 + * currentPage: + * type: integer + * example: 1 + * data: + * type: array + * items: + * type: object + * properties: + * user_name: + * type: string + * example: Lewis + * email: + * type: string + * example: lewis@gmail.com + * 401: + * description: Unauthorized + * 500: + * description: Server Error + */ + async getAllUsers(req: Request, res: Response) { try { const users = await this.userService.getAllUsers(); @@ -76,41 +183,239 @@ class UserController { } } + /** + * @swagger + * /api/v1/users/{id}: + * patch: + * tags: + * - User + * summary: Update a user + * description: API endpoint that allows authenticated super admins to update a single user's details. This endpoint ensures that only users with super admin privileges can modify user information, maintaining system security. + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: The ID of the user to update + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * firstName: + * type: string + * example: New + * lastName: + * type: string + * email: + * type: string + * role: + * type: string + * password: + * type: string + * isVerified: + * type: boolean + * example: true + * responses: + * 200: + * description: Successfully updated the user + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: success + * status_code: + * type: number + * example: 200 + * data: + * type: object + * properties: + * id: + * type: string + * email: + * type: string + * role: + * type: string + * created_at: + * type: string + * format: date-time + * updated_at: + * type: string + * format: date-time + * 422: + * description: Validation error + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: Error + * status_code: + * type: integer + * example: 422 + * message: + * type: string + * example: "Invalid user details provided." + * 403: + * description: Access denied + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: Error + * status_code: + * type: integer + * example: 403 + * message: + * type: string + * example: "Access denied. Super admin privileges required." + * 404: + * description: User not found + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: unsuccessful + * status_code: + * type: integer + * example: 404 + * message: + * type: string + * example: "User with id '123' not found" + * 500: + * description: Server Error + */ + async updateUserById() {} + + /** + * @swagger + * /api/v1/user/{id}: + * delete: + * tags: + * - User + * summary: Delete a user + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: The ID of the user + * responses: + * 202: + * description: User deleted successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: success + * status_code: + * type: number + * example: 202 + * message: + * type: string + * example: User deleted successfully + * 400: + * description: Bad request + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: unsuccessful + * status_code: + * type: number + * example: 400 + * message: + * type: string + * example: Valid id must be provided + * 404: + * description: Not found + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: unsuccessful + * status_code: + * type: number + * example: 404 + * message: + * type: string + * example: User not found + * 500: + * description: Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: unsuccessful + * status_code: + * type: number + * example: 500 + * message: + * type: string + * example: Failed to perform soft delete + */ async deleteUser(req: Request, res: Response) { const id = req.params.id; if (!id || !isUUID(id)) { return res.status(400).json({ - status: "unsuccesful", - status_code: 400, - message: "Valid id must be provided", - }); + status: "unsuccesful", + status_code: 400, + message: "Valid id must be provided", + }); } try { - await this.userService.softDeleteUser(id); return res.status(202).json({ - status: "sucess", - message: "User deleted successfully", - status_code: 202, - }); - + status: "sucess", + message: "User deleted successfully", + status_code: 202, + }); } catch (error) { - if (error instanceof HttpError) { return res.status(error.status_code).json({ - message: error.message + message: error.message, }); } else { return res.status(500).json({ - message: error.message || "Internal Server Error" + message: error.message || "Internal Server Error", }); } - } } } -export default UserController; \ No newline at end of file +export default UserController; diff --git a/src/routes/sms.ts b/src/routes/sms.ts index de9e1594..19ea3b2e 100644 --- a/src/routes/sms.ts +++ b/src/routes/sms.ts @@ -1,9 +1,7 @@ import { Router } from "express"; -import { sendSms } from "../controllers/SmsController"; -import { authMiddleware } from "../middleware"; const smsRouter = Router(); -smsRouter.post("/send", authMiddleware, sendSms); +// smsRouter.post("/send", authMiddleware, sendSms); export { smsRouter }; diff --git a/src/schema/product.schema.ts b/src/schema/product.schema.ts index 80160643..ded05c53 100644 --- a/src/schema/product.schema.ts +++ b/src/schema/product.schema.ts @@ -1,4 +1,4 @@ -import { number, object, string, TypeOf } from "zod"; +import { boolean, number, object, string, TypeOf } from "zod"; /** * @openapi @@ -55,6 +55,12 @@ const payload = { }), }; +const paginationSchema = object({ + totalItems: number(), + totalPages: number(), + currentPage: number(), +}); + const params = { params: object({ productId: string({ @@ -80,7 +86,16 @@ export const getProductSchema = object({ ...params, }); +export const getAllProductSchema = object({ + success: boolean(), + message: string(), + status_code: number(), + pagination: paginationSchema, + ...payload, +}); + export type CreateProductInput = TypeOf; export type UpdateProductInput = TypeOf; export type ReadProductInput = TypeOf; export type DeleteProductInput = TypeOf; +export type GetAllProductsResponse = TypeOf; diff --git a/src/schema/user.schema.ts b/src/schema/user.schema.ts new file mode 100644 index 00000000..b5cc23c8 --- /dev/null +++ b/src/schema/user.schema.ts @@ -0,0 +1,137 @@ +import { array, number, object, string, TypeOf } from "zod"; + +/** + * @openapi + * components: + * schemas: + * User: + * type: object + * properties: + * user_name: + * type: string + * example: "Lewis" + * email: + * type: string + * example: "lewis@gmail.com" + * UserProfile: + * type: object + * properties: + * id: + * type: string + * example: "58b6" + * user_name: + * type: string + * example: "yasuke" + * email: + * type: string + * example: "sam@gmail.com" + * profile_picture: + * type: string + * example: "https://avatar.com" + * bio: + * type: string + * example: "Developer at HNG" + * GetProfileResponse: + * type: object + * properties: + * status_code: + * type: number + * example: 200 + * data: + * $ref: '#/components/schemas/UserProfile' + * GetAllUsersResponse: + * type: object + * properties: + * status: + * type: string + * example: "success" + * status_code: + * type: number + * example: 200 + * message: + * type: string + * example: "Users retrieved successfully" + * pagination: + * type: object + * properties: + * totalItems: + * type: number + * example: 100 + * totalPages: + * type: number + * example: 10 + * currentPage: + * type: number + * example: 1 + * data: + * type: array + * items: + * $ref: '#/components/schemas/User' + * DeleteUserResponse: + * type: object + * properties: + * status: + * type: string + * example: "success" + * status_code: + * type: number + * example: 202 + * message: + * type: string + * example: "User deleted successfully" + */ + +const payload = object({ + id: string(), + user_name: string(), + email: string(), + profile_picture: string(), + bio: string(), +}); + +const paginationSchema = object({ + totalItems: number(), + totalPages: number(), + currentPage: number(), +}); + +const params = { + params: object({ + id: string({ + required_error: "userId is required", + }), + }), +}; + +export const getUserProfileSchema = object({ + response: object({ + status_code: number(), + data: payload, + }), +}); + +export const getAllUsersSchema = object({ + response: object({ + status: string(), + status_code: number(), + message: string(), + pagination: paginationSchema, + data: array(payload), + }), +}); + +export const deleteUserSchema = object({ + ...params, + response: object({ + status: string(), + status_code: number(), + message: string(), + }), +}); + +export type GetUserProfileResponse = TypeOf< + typeof getUserProfileSchema +>["response"]; +export type GetAllUsersResponse = TypeOf["response"]; +export type DeleteUserInput = TypeOf; +export type DeleteUserResponse = TypeOf["response"]; diff --git a/src/services/auth.services.ts b/src/services/auth.services.ts index a27bf008..cf8162a7 100644 --- a/src/services/auth.services.ts +++ b/src/services/auth.services.ts @@ -1,12 +1,12 @@ +import jwt from "jsonwebtoken"; +import config from "../config"; import AppDataSource from "../data-source"; -import { Profile, User } from "../models"; -import { IAuthService, IUserSignUp, IUserLogin } from "../types"; import { Conflict, HttpError } from "../middleware"; -import { hashPassword, generateNumericOTP, comparePassword } from "../utils"; +import { Profile, User } from "../models"; +import { IAuthService, IUserLogin, IUserSignUp } from "../types"; +import { comparePassword, generateNumericOTP, hashPassword } from "../utils"; import { Sendmail } from "../utils/mail"; -import jwt from "jsonwebtoken"; import { compilerOtp } from "../views/welcome"; -import config from "../config"; export class AuthService implements IAuthService { public async signUp(payload: IUserSignUp): Promise<{ @@ -45,7 +45,7 @@ export class AuthService implements IAuthService { config.TOKEN_SECRET, { expiresIn: "1d", - } + }, ); const mailSent = await Sendmail({ @@ -68,7 +68,7 @@ export class AuthService implements IAuthService { public async verifyEmail( token: string, - otp: number + otp: number, ): Promise<{ message: string }> { try { const decoded: any = jwt.verify(token, config.TOKEN_SECRET); @@ -96,7 +96,7 @@ export class AuthService implements IAuthService { } public async login( - payload: IUserLogin + payload: IUserLogin, ): Promise<{ access_token: string; user: Partial }> { const { email, password } = payload; From 6704f2553bb87769bd7b603e13f466c6b247f52f Mon Sep 17 00:00:00 2001 From: masterchief-Dave Date: Wed, 24 Jul 2024 21:21:20 +0100 Subject: [PATCH 2/2] feat: create documentation for user routes --- src/routes/sms.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/sms.ts b/src/routes/sms.ts index 19ea3b2e..de9e1594 100644 --- a/src/routes/sms.ts +++ b/src/routes/sms.ts @@ -1,7 +1,9 @@ import { Router } from "express"; +import { sendSms } from "../controllers/SmsController"; +import { authMiddleware } from "../middleware"; const smsRouter = Router(); -// smsRouter.post("/send", authMiddleware, sendSms); +smsRouter.post("/send", authMiddleware, sendSms); export { smsRouter };