From 81b526374757c558b98d31f715f310fdd21c9df2 Mon Sep 17 00:00:00 2001 From: Ashade Samson Date: Wed, 24 Jul 2024 19:06:38 +0100 Subject: [PATCH 1/2] feat: super admin gets logs of activities --- src/controllers/AdminController.ts | 85 ++++++++++++++++++++++++++++-- src/models/index.ts | 1 + src/models/log.ts | 19 +++++++ src/routes/admin.ts | 10 ++-- src/services/admin.services.ts | 42 +++++++++++++-- 5 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 src/models/log.ts diff --git a/src/controllers/AdminController.ts b/src/controllers/AdminController.ts index 24188c25..6f8b0110 100644 --- a/src/controllers/AdminController.ts +++ b/src/controllers/AdminController.ts @@ -1,5 +1,9 @@ import { Request, Response } from "express"; -import { AdminOrganisationService, AdminUserService } from "../services"; +import { + AdminOrganisationService, + AdminUserService, + AdminLogService, +} from "../services"; import { HttpError } from "../middleware"; import { check, param, validationResult } from "express-validator"; import { UserRole } from "../enums/userRoles"; @@ -45,8 +49,14 @@ class AdminOrganisationController { async setUserRole(req: Request, res: Response): Promise { try { - await param("user_id").isUUID().withMessage("Valid user ID must be provided.").run(req); - await check("role").isIn(Object.values(UserRole)).withMessage("Valid role must be provided.").run(req); + await param("user_id") + .isUUID() + .withMessage("Valid user ID must be provided.") + .run(req); + await check("role") + .isIn(Object.values(UserRole)) + .withMessage("Valid role must be provided.") + .run(req); const errors = validationResult(req); @@ -69,7 +79,9 @@ class AdminOrganisationController { }, }); } catch (error) { - res.status(error.status_code || 500).json({ message: error.message || "Internal Server Error" }); + res + .status(error.status_code || 500) + .json({ message: error.message || "Internal Server Error" }); } } } @@ -165,4 +177,67 @@ class AdminUserController { } } -export default { AdminOrganisationController, AdminUserController }; +// Get activities log +class AdminLogController { + private adminLogService: AdminLogService; + + constructor() { + this.adminLogService = new AdminLogService(); + } + + async getLogs(req: Request, res: Response): Promise { + try { + await check("page") + .optional() + .isInt({ min: 1 }) + .withMessage("Page must be a positive integer.") + .run(req); + await check("limit") + .optional() + .isInt({ min: 1 }) + .withMessage("Limit must be a positive integer.") + .run(req); + await check("sort") + .optional() + .isIn(["asc", "desc"]) + .withMessage('Sort must be either "asc" or "desc".') + .run(req); + await check("offset") + .optional() + .isInt({ min: 0 }) + .withMessage("Offset must be a non-negative integer.") + .run(req); + + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(422).json({ + status: "unsuccessful", + status_code: 422, + message: errors.array()[0].msg, + }); + return; + } + + const data = await this.adminLogService.getPaginatedLogs(req); + res.status(200).json({ + status: "success", + status_code: 200, + data, + }); + } catch (error) { + if (error instanceof HttpError) { + res.status(error.status_code).json({ message: error.message }); + } else { + res + .status(500) + .json({ message: error.message || "Internal Server Error" }); + } + } + } +} + +export default { + AdminOrganisationController, + AdminUserController, + AdminLogController, +}; diff --git a/src/models/index.ts b/src/models/index.ts index 25b8e3e7..9db15311 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -14,3 +14,4 @@ export * from "./emailQueue"; export * from "./comment"; export * from "./category"; export * from "./payment"; +export * from "./log"; diff --git a/src/models/log.ts b/src/models/log.ts new file mode 100644 index 00000000..f68d4470 --- /dev/null +++ b/src/models/log.ts @@ -0,0 +1,19 @@ +import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; + +@Entity() +export class Log { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column() + user_id: string; + + @Column() + action: string; + + @Column() + details: string; + + @Column() + timestamp: Date; +} diff --git a/src/routes/admin.ts b/src/routes/admin.ts index 9e52fcb3..2a691b49 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -9,6 +9,7 @@ const adminRouter = Router(); const adminOrganisationController = new admin.AdminOrganisationController(); const adminUserController = new admin.AdminUserController(); +const adminLogController = new admin.AdminLogController(); // Organisation adminRouter.patch( @@ -37,9 +38,10 @@ adminRouter.patch( ); adminRouter.post( - "/users/:user_id/roles", authMiddleware, - checkPermissions([UserRole.SUPER_ADMIN]), - adminOrganisationController.setUserRole.bind(adminOrganisationController), - ); + "/users/:user_id/roles", + authMiddleware, + checkPermissions([UserRole.SUPER_ADMIN]), + adminOrganisationController.setUserRole.bind(adminOrganisationController), +); export { adminRouter }; diff --git a/src/services/admin.services.ts b/src/services/admin.services.ts index 18b6062e..da862147 100644 --- a/src/services/admin.services.ts +++ b/src/services/admin.services.ts @@ -1,7 +1,7 @@ // / src/services/AdminOrganisationService.ts import { NextFunction, Request, Response } from "express"; // import { getRepository, Repository } from 'typeorm'; -import { User, Organization } from "../models"; +import { User, Organization, Log } from "../models"; import AppDataSource from "../data-source"; import { HttpError } from "../middleware"; import { hashPassword } from "../utils/index"; @@ -52,11 +52,11 @@ export class AdminOrganisationService { const user = await userRepository.findOne({ where: { id: user_id }, }); - + if (!user) { throw new HttpError(404, "User not Found"); } - + // Update User Role on the Database user.role = role; await userRepository.save(user); @@ -122,3 +122,39 @@ export class AdminUserService { } } } + +export class AdminLogService { + public async getPaginatedLogs(req: Request): Promise<{ + logs: Log[]; + totalLogs: number; + totalPages: number; + currentPage: number; + }> { + try { + const { page = 1, limit = 10, sort = "desc", offset = 0 } = req.query; + const logRepository = AppDataSource.getRepository(Log); + + const [logs, totalLogs] = await logRepository.findAndCount({ + order: { id: sort === "asc" ? "ASC" : "DESC" }, + skip: Number(offset), + take: Number(limit), + }); + + const totalPages = Math.ceil(totalLogs / Number(limit)); + + if (!logs.length) { + throw new HttpError(404, "Logs not found"); + } + + return { + logs, + totalLogs, + totalPages, + currentPage: Number(page), + }; + } catch (error) { + console.error(error); + throw new HttpError(error.status || 500, error.message || error); + } + } +} From bd3369ebbfd5675e76138f40545877a14ad1cdca Mon Sep 17 00:00:00 2001 From: phoenix Date: Wed, 24 Jul 2024 22:17:14 +0100 Subject: [PATCH 2/2] fix: fixed asynchronous operations while runing test --- docs/sendEmail.md | 2 +- package.json | 2 +- src/controllers/NotificationController.ts | 1 - src/controllers/ProductController.ts | 15 ++++----------- src/controllers/exportController.ts | 1 - src/controllers/updateBlogController.ts | 6 ------ src/index.ts | 2 +- src/middleware/testimonial.validation.ts | 2 +- src/services/admin.services.ts | 3 --- src/services/blog.services.ts | 1 - src/services/help.services.ts | 8 +++----- src/services/sendEmail.services.ts | 5 ----- src/services/updateBlog.services.ts | 4 +--- 13 files changed, 12 insertions(+), 40 deletions(-) diff --git a/docs/sendEmail.md b/docs/sendEmail.md index e1b3001c..357bcfd6 100644 --- a/docs/sendEmail.md +++ b/docs/sendEmail.md @@ -261,7 +261,7 @@ export class EmailService { } async sendEmail(payload: EmailQueuePayload): Promise { - console.log(`Sending email to ${payload.recipient} using template ${payload.templateId} with variables:`, payload.variables); + try { // Actual email sending logic here diff --git a/package.json b/package.json index a58afa61..3413ed3c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start:dev": "ts-node-dev --respawn --transpile-only ./src/index", "start": "ts-node --transpile-only src/index.ts", - "test": "jest ", + "test": "jest --forceExit", "test:email": "jest views/email/tests", "typeorm": "typeorm-ts-node-commonjs", "build": "tsc", diff --git a/src/controllers/NotificationController.ts b/src/controllers/NotificationController.ts index f2ceac79..895ef1bd 100644 --- a/src/controllers/NotificationController.ts +++ b/src/controllers/NotificationController.ts @@ -144,7 +144,6 @@ const CreateNotification = async (req: Request, res: Response) => { }); } } catch (error) { - console.log(error); res.status(500).json({ status: "error", code: 500, diff --git a/src/controllers/ProductController.ts b/src/controllers/ProductController.ts index 567ad3d0..5acdadcc 100644 --- a/src/controllers/ProductController.ts +++ b/src/controllers/ProductController.ts @@ -159,7 +159,6 @@ export class ProductController { message: err.message, status_code: 500, }); - console.error(err); } } } @@ -251,26 +250,24 @@ export class ProductController { */ async fetchProductById(req: Request, res: Response) { - const productId = req.params.product_id; - if(isNaN(Number(productId))){ + if (isNaN(Number(productId))) { return res.status(400).json({ status: "Bad Request", message: "Invalid Product Id", status_code: 400, - }) + }); } try { - const product = await this.productService.getOneProduct(productId) + const product = await this.productService.getOneProduct(productId); if (!product) { return res.status(404).json({ status: "Not found", message: "Product not found", status_code: 404, }); - } return res.status(200).json(product); } catch (error) { @@ -280,7 +277,6 @@ export class ProductController { status_code: 500, }); } - } /** @@ -565,10 +561,7 @@ export class ProductController { * type: string * example: Product not found */ - async deleteProduct(req: Request, res: Response) { - - - } + async deleteProduct(req: Request, res: Response) {} } export default ProductController; diff --git a/src/controllers/exportController.ts b/src/controllers/exportController.ts index 35f4f839..0d28ded6 100644 --- a/src/controllers/exportController.ts +++ b/src/controllers/exportController.ts @@ -44,7 +44,6 @@ class exportController { try { const format = req.query.format as string; const userId = req.user.id; - console.log(`Requested format: ${format}`); const user = await ExportService.getUserById(userId); if (!user) { diff --git a/src/controllers/updateBlogController.ts b/src/controllers/updateBlogController.ts index 8c4a488a..a5b88741 100644 --- a/src/controllers/updateBlogController.ts +++ b/src/controllers/updateBlogController.ts @@ -5,9 +5,6 @@ export const updateBlogController = async (req: Request, res: Response) => { const { id } = req.params; const { title, content, published_at, image_url } = req.body; - console.log("Controller - updateBlogController called with id:", id); - console.log("Controller - Request body:", req.body); - try { const updatedBlog = await updateBlogPost( id, @@ -16,7 +13,6 @@ export const updateBlogController = async (req: Request, res: Response) => { published_at, image_url, ); - console.log("Controller - Blog post updated successfully:", updatedBlog); return res.status(200).json({ status: "success", @@ -25,8 +21,6 @@ export const updateBlogController = async (req: Request, res: Response) => { data: updatedBlog, }); } catch (error) { - console.error("Controller - Error updating blog post:", error); - return res.status(500).json({ status: "unsuccessful", status_code: 500, diff --git a/src/index.ts b/src/index.ts index 75278881..8cba697c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -96,6 +96,6 @@ AppDataSource.initialize() log.info(`Server is listening on port ${port}`); }); }) - .catch((error) => console.error(error)); + .catch((error) => log.error(error)); export default server; diff --git a/src/middleware/testimonial.validation.ts b/src/middleware/testimonial.validation.ts index aec8a8fb..1e23955a 100644 --- a/src/middleware/testimonial.validation.ts +++ b/src/middleware/testimonial.validation.ts @@ -8,7 +8,7 @@ export const validateTestimonial = [ body("testimonial").notEmpty().withMessage("Testimonial is required"), (req: Request, res: Response, next: NextFunction) => { const errors = validationResult(req); - console.log(errors); + if (!errors.isEmpty()) { throw new InvalidInput("Validation failed"); } diff --git a/src/services/admin.services.ts b/src/services/admin.services.ts index da862147..a5992d75 100644 --- a/src/services/admin.services.ts +++ b/src/services/admin.services.ts @@ -37,7 +37,6 @@ export class AdminOrganisationService { }); return newOrg; } catch (error) { - console.error(error); throw new HttpError(error.status || 500, error.message || error); } } @@ -117,7 +116,6 @@ export class AdminUserService { }); return updatedUser!; } catch (error) { - console.error(error); throw new HttpError(error.status || 500, error.message || error); } } @@ -153,7 +151,6 @@ export class AdminLogService { currentPage: Number(page), }; } catch (error) { - console.error(error); throw new HttpError(error.status || 500, error.message || error); } } diff --git a/src/services/blog.services.ts b/src/services/blog.services.ts index f2e87ae1..aee67fe2 100644 --- a/src/services/blog.services.ts +++ b/src/services/blog.services.ts @@ -29,7 +29,6 @@ export class BlogService { const result = await this.blogRepository.delete(id); return result.affected !== 0; } catch (error) { - //console.error('Error deleting blog post:', error); throw new Error("Error deleting blog post"); } } diff --git a/src/services/help.services.ts b/src/services/help.services.ts index 71cf3697..48357336 100644 --- a/src/services/help.services.ts +++ b/src/services/help.services.ts @@ -16,7 +16,7 @@ export class HelpService { if (!title || !content || !author) { throw new HttpError( 422, - "Validation failed: Title, content, and author are required" + "Validation failed: Title, content, and author are required", ); } @@ -67,7 +67,6 @@ export class HelpService { }); return newArticle; } catch (error) { - console.error(error); throw new HttpError(error.status || 500, error.message || error); } } @@ -76,7 +75,7 @@ export class HelpService { export const authMiddleware = ( req: Request, res: Response, - next: NextFunction + next: NextFunction, ) => { const authHeader = req.headers["authorization"]; const token = authHeader && authHeader.split(" ")[1]; @@ -106,7 +105,7 @@ export const authMiddleware = ( export const verifyAdmin = async ( req: Request, res: Response, - next: NextFunction + next: NextFunction, ) => { const authHeader = req.headers["authorization"]; const token = authHeader && authHeader.split(" ")[1]; @@ -125,7 +124,6 @@ export const verifyAdmin = async ( const user = await userRepository.findOne({ where: { id: decodedToken.userId }, }); - console.log(user.role); if (user.role !== "admin") { return res.status(403).json({ diff --git a/src/services/sendEmail.services.ts b/src/services/sendEmail.services.ts index f276c4df..800eb30e 100644 --- a/src/services/sendEmail.services.ts +++ b/src/services/sendEmail.services.ts @@ -88,11 +88,6 @@ export class EmailService { } async sendEmail(payload: EmailQueuePayload): Promise { - console.log( - `Sending email to ${payload.recipient} using template ${payload.templateId} with variables:`, - payload.variables, - ); - try { } catch (error) { throw new ServerError("Internal server error"); diff --git a/src/services/updateBlog.services.ts b/src/services/updateBlog.services.ts index cc4f4ea1..a587e987 100644 --- a/src/services/updateBlog.services.ts +++ b/src/services/updateBlog.services.ts @@ -34,9 +34,7 @@ export const updateBlogPost = async ( try { await blogRepository.save(blog); - } catch (error) { - console.error("Service - Error saving blog post:", error); - } + } catch (error) {} return blog; };