Skip to content

Commit 240bb3f

Browse files
authored
Merge pull request #50 from Kushal096/develop
Introduced Image Upload Support for members routes
2 parents 945b6f9 + c8ae4dd commit 240bb3f

7 files changed

Lines changed: 139 additions & 70 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "public"."members" ADD COLUMN "avatar_url" TEXT;

prisma/schema/auth.prisma

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
model User {
22
id String @id @default(uuid())
33
email String @unique @db.VarChar(255)
4-
name String
54
role Role @default(MODERATOR)
65
memberId String? @map("member_id")
76
createdAt DateTime @default(now()) @map("created_at")
87
updatedAt DateTime @updatedAt @map("updated_at")
98
emailVerified Boolean @default(false)
109
image String?
10+
name String
1111
accounts Account[]
1212
sessions Session[]
1313
member member? @relation(fields: [memberId], references: [id])
@@ -135,12 +135,13 @@ model EventSchedule {
135135
}
136136

137137
model member {
138-
id String @id @default(uuid())
139-
name String
140-
role String
141-
year DateTime
142-
status MemberStatus
143-
User User[]
138+
id String @id @default(uuid())
139+
name String
140+
avatarUrl String? @map("avatar_url")
141+
role String
142+
year DateTime
143+
status MemberStatus
144+
User User[]
144145
145146
@@map("members")
146147
}

src/controllers/member.controller.ts

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import { ErrorResponse, SuccessResponse } from "@/dtos";
22
import { memberServices } from "@/services/member.service";
3+
import { uploadImageToCloudinary } from "@/utils/cloudinary.uploader"
34
import { HTTP } from "@/utils/constants";
4-
import { error } from "console";
55
import type { Request, Response } from "express";
6-
import { success } from "zod";
7-
import { PaginationResponse } from "@/dtos";
6+
import { PaginationResponse } from "@/dtos";
87
class MemberController{
98
async createMember(req : Request, res : Response){
109
try {
11-
const {name,role,year} = req.body;
12-
const result = await memberServices.createMember({name,role,year});
10+
const memberData = req.body;
11+
const imageFile = req.file;
12+
13+
const result = await memberServices.createMember(
14+
memberData,
15+
imageFile
16+
);
1317
if (!result.success) {
1418
return res.status(HTTP.BAD_REQUEST).json(ErrorResponse(HTTP.BAD_REQUEST,result.error));
1519
}
@@ -22,62 +26,63 @@ class MemberController{
2226
}
2327

2428

25-
async getMembers(req: Request, res: Response) {
26-
try {
27-
const query = (req as any).validatedQuery;
28-
const page = query.page;
29-
const limit = query.limit;
30-
const skip = (page - 1) * limit;
29+
async getMembers(req: Request, res: Response) {
30+
try {
31+
const query = (req as any).validatedQuery;
32+
const page = query.page;
33+
const limit = query.limit;
34+
const skip = (page - 1) * limit;
3135

32-
const result = await memberServices.getMembers({ skip, limit });
36+
const result = await memberServices.getMembers({ skip, limit });
37+
38+
if (!result.success) {
39+
return res
40+
.status(HTTP.NOT_FOUND)
41+
.json(ErrorResponse(HTTP.NOT_FOUND, result.error));
42+
}
43+
44+
if (!result.data) {
45+
return res
46+
.status(HTTP.NOT_FOUND)
47+
.json(ErrorResponse(HTTP.NOT_FOUND, "No member data found"));
48+
}
49+
const { members, total } = result.data;
3350

34-
if (!result.success) {
3551
return res
36-
.status(HTTP.NOT_FOUND)
37-
.json(ErrorResponse(HTTP.NOT_FOUND, result.error));
38-
}
52+
.status(HTTP.OK)
53+
.json(
54+
PaginationResponse(
55+
HTTP.OK,
56+
"Members fetched successfully",
57+
members,
58+
total,
59+
page,
60+
limit
61+
)
62+
);
3963

40-
if (!result.data) {
64+
} catch (error) {
65+
console.log(`Get Members Controller Error: ${error}`);
4166
return res
42-
.status(HTTP.NOT_FOUND)
43-
.json(ErrorResponse(HTTP.NOT_FOUND, "No member data found"));
67+
.status(HTTP.INTERNAL)
68+
.json(
69+
ErrorResponse(
70+
HTTP.INTERNAL,
71+
(error as Error).message || "Internal Server Error"
72+
)
73+
);
4474
}
45-
const { members, total } = result.data;
46-
47-
return res
48-
.status(HTTP.OK)
49-
.json(
50-
PaginationResponse(
51-
HTTP.OK,
52-
"Members fetched successfully",
53-
members,
54-
total,
55-
page,
56-
limit
57-
)
58-
);
59-
60-
} catch (error) {
61-
console.log(`Get Members Controller Error: ${error}`);
62-
return res
63-
.status(HTTP.INTERNAL)
64-
.json(
65-
ErrorResponse(
66-
HTTP.INTERNAL,
67-
(error as Error).message || "Internal Server Error"
68-
)
69-
);
7075
}
71-
}
7276

7377

7478
async updateMember(req : Request, res : Response){
7579

7680
try {
7781
const memberId = req.params.id
7882
const updates = req.body
83+
const imageFile = req.file
7984

80-
const result = await memberServices.updateMember(memberId, updates)
85+
const result = await memberServices.updateMember(memberId, updates,imageFile)
8186
if (!result.success){
8287
return res.status(HTTP.BAD_REQUEST).json(ErrorResponse(HTTP.BAD_REQUEST, result.error))
8388
}

src/routers/member.router.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { memberController } from '@/controllers/member.controller';
2+
import upload from '@/lib/multer';
23
import { createMemberSchema, getMembersQuerySchema, memberParamsSchema, updateMemberSchema } from '@/lib/zod/member.schema';
34
import { authMiddleware, isModerator } from '@/middleware/auth.middleware';
45
import { validateBody, validateParams, validateQuery } from '@/middleware/validation.middleware';
@@ -11,10 +12,9 @@ const memberRouter = Router();
1112
memberRouter.get('/',validateQuery(getMembersQuerySchema) ,memberController.getMembers)
1213

1314
//Authenticated routes
14-
memberRouter.use(authMiddleware)
15-
memberRouter.use(isModerator)
16-
memberRouter.post('/',validateBody(createMemberSchema), memberController.createMember )
17-
memberRouter.patch("/:id",validateParams(memberParamsSchema) ,validateBody(updateMemberSchema) ,memberController.updateMember)
15+
memberRouter.use(authMiddleware,isModerator)
16+
memberRouter.post('/',upload.single("avatar") ,validateBody(createMemberSchema), memberController.createMember )
17+
memberRouter.patch("/:id",upload.single("avatar"), validateParams(memberParamsSchema) ,validateBody(updateMemberSchema) ,memberController.updateMember)
1818
memberRouter.get('/:id', validateParams(memberParamsSchema), memberController.getMember);
1919

2020

src/services/member.service.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,91 @@
11
import prisma from "@/db/prisma";
22
import { prismaSafe } from "@/lib/prismaSafe";
33
import type { UpdateMemberInput,CreateMemberInput } from "@/lib/zod/member.schema";
4+
import { uploadImageToCloudinary } from "@/utils/cloudinary.uploader";
5+
import { profile } from "console";
46

57
class MemberServices{
6-
async createMember(member: CreateMemberInput ){
8+
async createMember(member: CreateMemberInput , imageFile : Express.Multer.File | undefined){
79
try {
10+
let profileImageUrl : string | null = null;
11+
12+
if(imageFile){
13+
try {
14+
const uploadResult = await uploadImageToCloudinary(imageFile.path,{
15+
folder : "profile_images",
16+
})
17+
if(!uploadResult.success){
18+
console.log(`Error while uploading image: ${uploadResult.error}`)
19+
}
20+
profileImageUrl = uploadResult.url ?? null
21+
22+
} catch (error) {
23+
console.log(`Cloudinary upload error: ${error}`)
24+
}
25+
}
26+
827
const [memberError, memberResult] = await prismaSafe(
928
prisma.member.create({
1029
data: {
1130
status : 'ACTIVE',
12-
...member
31+
...member,
32+
avatarUrl : profileImageUrl,
1333
}
1434

1535
})
1636
)
17-
if(memberError) {
18-
return {success:false, error:memberError};
19-
}
37+
if(memberError) {
38+
return {success:false, error:memberError};
39+
}
2040
if(!memberResult) {
2141
return {success:false, error:'Failed to create member'}
2242
}
2343
return {success : true, data:memberResult}
24-
} catch (error) {
44+
} catch (error) {
2545
console.log(`Failed to create Member, ${error}`)
2646
return {success : false, error: error}
2747
}
2848

2949
}
3050

31-
async updateMember(memberId: string, updates: UpdateMemberInput){
51+
async updateMember(memberId: string, updates: UpdateMemberInput, imageFile : Express.Multer.File | undefined){
3252
try {
33-
let modifiedUpdates = { ...updates };
53+
54+
let profileImageUrl : string | null = null;
55+
56+
if(imageFile){
57+
try {
58+
const uploadResult = await uploadImageToCloudinary(imageFile.path,{
59+
folder : "profile_images",
60+
})
61+
if(!uploadResult.success){
62+
console.log(`Error while uploading image: ${uploadResult.error}`)
63+
}
64+
profileImageUrl = uploadResult.url ?? null
65+
66+
} catch (error) {
67+
console.log(`Cloudinary upload error: ${error}`)
68+
}
69+
}
3470

3571

36-
if (Object.keys(modifiedUpdates).length === 0) {
37-
return { success: false, error: "No valid fields provided for update" };
72+
if (Object.keys(updates).length === 0 && !profileImageUrl) {
73+
return { success: false, error: "No valid fields provided for updates" };
3874
}
3975

4076
const [error, result] = await prismaSafe(
4177
prisma.member.update({
4278
where: { id: memberId },
43-
data: modifiedUpdates,
79+
data: {...updates, avatarUrl : profileImageUrl}
4480
})
4581
);
4682

4783
if (error) return { success: false, error };
48-
if (!result) return { success: false, error: "Failed to update member" };
84+
if (!result) return { success: false, error: "Failed to updates member" };
4985

5086
return { success: true, data: result };
5187
} catch (error) {
52-
console.log(`Failed to update member, ${error}`);
88+
console.log(`Failed to updates member, ${error}`);
5389
return { success: false, error };
5490
}
5591
}

src/types/member.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface Member{
22
name : string;
33
role : string;
4+
avatarUrl : string;
45
year : Date;
56
status? : "ACTIVE" | "INACTIVE"
67
}

src/utils/cloudinary.uploader.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import cloudinary from "@/lib/cloudinary";
2+
3+
export const uploadImageToCloudinary = async (
4+
filePath: string,
5+
options: { folder?: string } = {}
6+
): Promise<{ success: boolean; url?: string; error?: string }> => {
7+
try {
8+
const result = await cloudinary.uploader.upload(filePath, {
9+
folder: options.folder || "uploads",
10+
resource_type: "image",
11+
});
12+
13+
return {
14+
success: true,
15+
url: result.secure_url,
16+
};
17+
} catch (error: any) {
18+
console.error("Cloudinary Upload Error:", error);
19+
return {
20+
success: false,
21+
error: error.message || "Upload failed",
22+
};
23+
}
24+
};

0 commit comments

Comments
 (0)