Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend #166

Merged
merged 14 commits into from
Mar 22, 2025
Merged
9 changes: 8 additions & 1 deletion backend/src/constants/response-message.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,12 @@ export const HttpResponse = {
SAME_USERNAME: "Cannot change to old username",
RESOURCE_FOUND: "Resource found.",
RESOURCE_UPDATED: "Resource updated.",
PROFILE_PICTURE_CHANGED: "Profile picture changed successfully"
PROFILE_PICTURE_CHANGED: "Profile picture changed successfully",

BLOG_NOT_FOUND: "Blog not found",
INVALID_ID: "Invalid ID format",
REQUIRED_AUTHOR_ID: "Author ID is required",
REQUIRED_AUTHOR_NAME: "Author name is required",
REQUIRED_TITLE: "Blog title is required",
REQUIRED_CONTENT: "Blog content is required",
};
87 changes: 87 additions & 0 deletions backend/src/controllers/implementation/blog.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { NextFunction, Request, Response } from "express";
import { IBlogController } from "../interface/IBlogController";
import { IBlogService } from "@/services/interface/IBlogService";
import { Types } from "mongoose";
import { HttpStatus } from "@/constants";
import { CreateBlogRequestType } from "@/schema/create-blog.schema";
import { EditBlogRequestType } from "@/schema";

export class BlogController implements IBlogController {
constructor(private blogService: IBlogService) {}

async createBlog(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const blogData = req.body as CreateBlogRequestType;
const { id } = JSON.parse(req.headers["x-user-payload"] as string);
const createdBlog = await this.blogService.createBlog({
...blogData,
authorId: id,
});
res.status(HttpStatus.CREATED).json(createdBlog);
} catch (error) {
next(error);
}
}

async getBlogById(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const blogId = new Types.ObjectId(req.params.id);
const blog = await this.blogService.getBlogById(blogId);
res.status(HttpStatus.OK).json(blog);
} catch (error) {
next(error);
}
}

async getAllBlogs(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const blogs = await this.blogService.getAllBlogs();
res.status(HttpStatus.OK).json(blogs);
} catch (error) {
next(error);
}
}

async updateBlog(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const blogId = new Types.ObjectId(req.params.id);
const updateData = req.body as EditBlogRequestType;
const updatedBlog = await this.blogService.updateBlog(blogId, updateData);

res.status(HttpStatus.OK).json(updatedBlog);
} catch (error) {
next(error);
}
}

async deleteBlog(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const blogId = new Types.ObjectId(req.params.id);
const deletedBlog = await this.blogService.deleteBlog(blogId);

res.status(HttpStatus.OK).json(deletedBlog);
} catch (error) {
next(error);
}
}
}
9 changes: 9 additions & 0 deletions backend/src/controllers/interface/IBlogController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextFunction, Request, Response } from "express";

export interface IBlogController {
createBlog(req: Request, res: Response, next: NextFunction): Promise<void>;
getBlogById(req: Request, res: Response, next: NextFunction): Promise<void>;
getAllBlogs(req: Request, res: Response, next: NextFunction): Promise<void>;
updateBlog(req: Request, res: Response, next: NextFunction): Promise<void>;
deleteBlog(req: Request, res: Response, next: NextFunction): Promise<void>;
}
10 changes: 6 additions & 4 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import cors from "cors";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";



dotenv.config();

//* validating all the env
Expand All @@ -23,14 +21,17 @@ import { notFoundHandler } from "./middlewares/not-found.middleware";
import { errorHandler } from "./middlewares/error.middlware";
import { env } from "./configs/env.config";
import profileRouter from "./routers/profile.router";
import blogRouter from "./routers/blog.router";

const app = express();
app.use(cors({
app.use(
cors({
origin: env.CLIENT_ORIGIN,
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
}));
})
);
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
Expand All @@ -40,6 +41,7 @@ connectRedis();

app.use("/api/auth", authRouter);
app.use("/api/profile", profileRouter);
app.use("/api/blog", blogRouter);
app.use(notFoundHandler);
app.use(errorHandler);

Expand Down
47 changes: 47 additions & 0 deletions backend/src/models/implementation/blog.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { model, Schema, Document, Types } from "mongoose";
import { IBlog } from "shared/types";

export interface IBlogModel extends Document, Omit<IBlog, "_id" | "authorId"> {
authorId: Types.ObjectId;
};
const blogSchema = new Schema<IBlogModel>(
{
authorId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
authorName: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
thumbnail: {
type: String,
},
content: {
type: String,
required: true,
},
tags: {
type: [String],
},
likes: {
type: Number,
default: 0,
},
comments: {
type: Number,
default: 0,
},
},
{
timestamps: { createdAt: "created_at", updatedAt: "updated_at" },
}
);

const Blog = model<IBlogModel>("Blog", blogSchema);
export default Blog;
37 changes: 37 additions & 0 deletions backend/src/repositories/implementation/blog.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BaseRepository } from "../base.repository";
import { IBlogRepository } from "../interface/IBlogRepository";
import Blog, { IBlogModel } from "@/models/implementation/blog.model";
import { Types } from "mongoose";

export class BlogRepository
extends BaseRepository<IBlogModel>
implements IBlogRepository
{
constructor() {
super(Blog);
}

async createBlog(blogData: Partial<IBlogModel>): Promise<IBlogModel> {
const newBlog = await this.create(blogData);
return newBlog;
}

async findBlogById(blogId: Types.ObjectId): Promise<IBlogModel | null> {
return this.findById(blogId);
}

async findAllBlogs(): Promise<IBlogModel[]> {
return this.findAll();
}

async updateBlog(
blogId: Types.ObjectId,
updateData: Partial<IBlogModel>
): Promise<IBlogModel | null> {
return this.update(blogId, updateData);
}

async deleteBlog(blogId: Types.ObjectId): Promise<IBlogModel | null> {
return this.delete(blogId);
}
}
13 changes: 13 additions & 0 deletions backend/src/repositories/interface/IBlogRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IBlogModel } from "@/models/implementation/blog.model";
import { Types } from "mongoose";

export interface IBlogRepository {
createBlog(blogData: Partial<IBlogModel>): Promise<IBlogModel>;
findBlogById(blogId: Types.ObjectId): Promise<IBlogModel | null>;
findAllBlogs(): Promise<IBlogModel[]>;
updateBlog(
blogId: Types.ObjectId,
updateData: Partial<IBlogModel>
): Promise<IBlogModel | null>;
deleteBlog(blogId: Types.ObjectId): Promise<IBlogModel | null>;
}
49 changes: 49 additions & 0 deletions backend/src/routers/blog.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Router } from "express";
import { validate } from "@/middlewares/validate.middleware";
import { BlogRepository } from "@/repositories/implementation/blog.repository";
import { BlogService } from "@/services/implementation/blog.service";
import { BlogController } from "@/controllers/implementation/blog.controller";
import { UserRepository } from "@/repositories/implementation/user.repository";
import { createBlogSchema, editBlogSchema } from "@/schema";
import authenticate from "@/middlewares/verify-token.middleware";

const router = Router();

const blogRepository = new BlogRepository();
const userRepository = new UserRepository();
const blogService = new BlogService(blogRepository, userRepository);
const blogController = new BlogController(blogService);

router.post(
"/",
validate(createBlogSchema),
authenticate("user"),
blogController.createBlog.bind(blogController)
);

router.get(
"/",
authenticate("user"),
blogController.getAllBlogs.bind(blogController)
);

router.get(
"/:id",
authenticate("user"),
blogController.getBlogById.bind(blogController)
);

router.put(
"/:id",
validate(editBlogSchema),
authenticate("user"),
blogController.updateBlog.bind(blogController)
);

router.delete(
"/:id",
authenticate("user"),
blogController.deleteBlog.bind(blogController)
);

export default router;
19 changes: 19 additions & 0 deletions backend/src/schema/create-blog.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { HttpResponse } from "@/constants/response-message.constant";
import { z } from "zod";

export const createBlogSchema = z
.object({
title: z
.string()
.min(1, HttpResponse.REQUIRED_TITLE)
.max(200, "Title must not exceed 200 characters"),
content: z
.string()
.min(1, HttpResponse.REQUIRED_CONTENT)
.max(5000, "Content must not exceed 5000 characters"),
thumbnail: z.string().url("Thumbnail must be a valid URL").optional(),
tags: z.array(z.string()).optional(),
})
.strict();

export type CreateBlogRequestType = z.infer<typeof createBlogSchema>;
6 changes: 6 additions & 0 deletions backend/src/schema/edit-blog-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from "zod";
import { createBlogSchema } from "./create-blog.schema";

export const editBlogSchema = createBlogSchema.partial();

export type EditBlogRequestType = z.infer<typeof editBlogSchema>;
16 changes: 9 additions & 7 deletions backend/src/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export { verifyOtpSchema } from "./verify-otp.schema"
export { signinSchema } from "./signin.schema"
export { signupSchema } from "./signup-schema"
export { editUsernameSchema } from "./username.schema"
export { updateProfileSchema } from "./update-profile.schema"
export { resetPasswordSchema } from "./reset-pass.schema"
export { verifyEmailSchema } from "./forgot-pass.schema"
export { verifyOtpSchema } from "./verify-otp.schema";
export { signinSchema } from "./signin.schema";
export { signupSchema } from "./signup-schema";
export { editUsernameSchema } from "./username.schema";
export { updateProfileSchema } from "./update-profile.schema";
export { resetPasswordSchema } from "./reset-pass.schema";
export { verifyEmailSchema } from "./forgot-pass.schema";
export { CreateBlogRequestType, createBlogSchema } from "./create-blog.schema";
export { EditBlogRequestType, editBlogSchema } from "./edit-blog-schema";
9 changes: 3 additions & 6 deletions backend/src/schema/username.schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@

import {z} from 'zod'
import { z } from "zod";
export const editUsernameSchema = z.object({
username: z
.string()
.min(3, "Username must be at least 3 characters long")
})
username: z.string().min(3, "Username must be at least 3 characters long"),
});
Loading
Loading