From eaaea4061353a8e7dc53220e6d7d09fcd4ab6731 Mon Sep 17 00:00:00 2001 From: AnkitNeupane007 Date: Fri, 10 Apr 2026 18:23:04 +0545 Subject: [PATCH 1/2] readme update --- README.md | 15 +++++- src/controllers/announcements/getAdmin.js | 2 +- src/swagger/docs/users.js | 47 +++++++++++++++++++ .../responses/announcementResponses.js | 17 +++++-- src/validators/responses/userResponses.js | 10 +++- 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2a0e8cc..44cf4f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 📢 NotifierAPI Backend -A robust, enterprise-grade Node.js backend API for managing user authentication, announcements, and notifications. Built with **Express.js** and **Prisma ORM**, this system is engineered for high availability, security, and scalability. +A robust, enterprise-grade Node.js backend API for managing user authentication, announcements, and notifications. Built with **Express.js** and **Prisma ORM**, this system is engineered for high availability, security, and scalability. It is fully **deployed and hosted on Microsoft Azure**, leveraging cloud-native features for optimal performance. --- @@ -23,6 +23,17 @@ A robust, enterprise-grade Node.js backend API for managing user authentication, --- +## ☁️ Azure Cloud Deployment + +NotifierAPI is designed for and actively **deployed on Microsoft Azure**. The deployment architecture takes advantage of Azure's robust ecosystem to ensure enterprise-grade reliability: + +- **Azure Virtual Machine:** The backend Node.js API is containerized via Docker and orchestrated on Azure for seamless scaling and zero-downtime deployments. +- **Neon Database for PostgreSQL:** A fully managed, highly available PostgreSQL instance ensures secure and resilient data storage. +- **Azure Cache for Redis:** Provides a distributed, low-latency rate-limiting and caching layer, protecting the API from abuse while keeping response times minimal. +- **CI/CD Integration:** Automated deployment pipelines ensure seamless updates directly to the Azure environment. + +--- + ## 🏗️ Backend Architecture NotifierAPI utilizes a modern, scalable Request Flow Architecture: @@ -144,4 +155,4 @@ Interactive API documentation is generated dynamically from Zod schemas. Once th ├── docker-compose.yml # Docker Compose configuration ├── Dockerfile # Container build instructions └── package.json # Dependencies and scripts -``` \ No newline at end of file +``` diff --git a/src/controllers/announcements/getAdmin.js b/src/controllers/announcements/getAdmin.js index c69c9c5..1e26928 100644 --- a/src/controllers/announcements/getAdmin.js +++ b/src/controllers/announcements/getAdmin.js @@ -61,7 +61,7 @@ const getAdminAnnouncements = async (req, res) => { page, limit, totalPages: Math.ceil(total / limit), - next: limit * page < total, + hasNext: limit * page < total, }, }, }); diff --git a/src/swagger/docs/users.js b/src/swagger/docs/users.js index 105a33a..dff5b72 100644 --- a/src/swagger/docs/users.js +++ b/src/swagger/docs/users.js @@ -20,6 +20,34 @@ export const userSwaggerDocs = { }, }, + uploadProfilePicture: { + method: "post", + path: "/user/me/profile-picture", + tags: ["Users"], + summary: "Upload profile picture", + security: [{ bearerAuth: [] }], + request: { + body: { + content: { + "multipart/form-data": { + schema: z.object({ + profilePicture: z.string().openapi({ + format: "binary", + description: "Profile picture image file", + }), + }), + }, + }, + }, + }, + responses: { + 200: { + description: "Profile picture uploaded successfully", + content: { "application/json": { schema: getMyselfResponseSchema } }, + }, + }, + }, + getUsers: { method: "get", path: "/user", @@ -40,6 +68,25 @@ export const userSwaggerDocs = { }, }, + getUser: { + method: "get", + path: "/user/{id}", + tags: ["Users"], + summary: "Get a user by ID (Admin)", + security: [{ bearerAuth: [] }], + request: { + params: z.object({ + id: z.string().openapi({ description: "User ID" }), + }), + }, + responses: { + 200: { + description: "User details", + content: { "application/json": { schema: getMyselfResponseSchema } }, + }, + }, + }, + deleteUser: { method: "delete", path: "/user/{id}", diff --git a/src/validators/responses/announcementResponses.js b/src/validators/responses/announcementResponses.js index 7b5b5c2..fb37c5b 100644 --- a/src/validators/responses/announcementResponses.js +++ b/src/validators/responses/announcementResponses.js @@ -3,6 +3,18 @@ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi"; extendZodWithOpenApi(z); +export const attachmentSchema = z.object({ + filename: z.string().openapi({ example: "document.pdf" }), + fileUrl: z + .string() + .openapi({ example: "/announcements/cm0z.../document.pdf" }), + fileType: z.string().openapi({ example: "application/pdf" }), + signedUrl: z.string().url().openapi({ + example: "https://supabase.../document.pdf?token=...", + description: "Signed URL valid for 1 hour", + }), +}); + export const announcementBaseSchema = z.object({ id: z.string().openapi({ example: "cm0z..." }), title: z.string().openapi({ example: "System Maintenance" }), @@ -80,9 +92,8 @@ export const getAnnouncementByIdResponseSchema = z .omit({ isRead: true, userId: true, updatedAt: true }) .extend({ isRead: z.boolean().optional(), - readAt: z.string().optional(), - attachments: z.array(z.any()).optional(), - submission: z.any().optional(), + readAt: z.string().nullable().optional(), + attachments: z.array(attachmentSchema).optional(), }), }), }) diff --git a/src/validators/responses/userResponses.js b/src/validators/responses/userResponses.js index 0c78b94..c336b27 100644 --- a/src/validators/responses/userResponses.js +++ b/src/validators/responses/userResponses.js @@ -11,7 +11,15 @@ export const userProfileSchema = z.object({ id: z.string().openapi({ example: "cm0z..." }), name: z.string().openapi({ example: "John Doe" }), email: z.string().email().openapi({ example: "user@example.com" }), - role: z.string().openapi({ example: "USER" }), + isEmailVerified: z.boolean().openapi({ example: false }), + profilePictureUrl: z.string().nullable().openapi({ + example: "https://...", + description: "Profile picture URL with cache-busting timestamp appended", + }), + updatedAt: z + .string() + .datetime() + .openapi({ example: "2026-04-10T10:00:00.000Z" }), }); export const getMyselfResponseSchema = z From 1317ed36f6738edead600cc3a46c433f1f0aa1e7 Mon Sep 17 00:00:00 2001 From: AnkitNeupane007 Date: Sat, 11 Apr 2026 00:13:15 +0545 Subject: [PATCH 2/2] fix profile pic --- src/controllers/users/profilePic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/users/profilePic.js b/src/controllers/users/profilePic.js index 25c9a79..2f7d982 100644 --- a/src/controllers/users/profilePic.js +++ b/src/controllers/users/profilePic.js @@ -14,7 +14,7 @@ const uploadProfilePicture = async (req, res) => { throw new AppError("Please upload a file", 400); } - if (!file.mimetype.startsWith("profilePicture/")) { + if (!file.mimetype.startsWith("image/")) { throw new AppError("Profile picture must be an image", 400); }