Skip to content

Commit 1edf380

Browse files
committed
Added Upload and Update Image functionality for Achievements
1 parent 1304a32 commit 1edf380

5 files changed

Lines changed: 712 additions & 95 deletions

File tree

Lines changed: 61 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { NextFunction, Request, Response } from "express";
1+
import { Request, Response } from "express";
22
import * as achievementService from "../services/achievement.service";
3+
import { uploadImage } from "../utils/imageUtils";
4+
import { supabase } from "../app";
35
import { ApiError } from "../utils/apiError";
46

57
export const getAchievements = async (req: Request, res: Response) => {
@@ -36,43 +38,34 @@ export const getAchievementById = async (req: Request, res: Response) => {
3638
});
3739
};
3840

41+
export const createAchievement = async (req: Request, res: Response) => {
42+
const file = req.file;
43+
if (!file) {
44+
throw new ApiError('Image file is not found', 400);
45+
}
3946

40-
// export const createAchievement = async (req: Request, res: Response) => {
41-
// const { title, achievedAt, imageUrl} = req.body;
42-
43-
// if (!title || !achievedAt ) {
44-
// throw new ApiError("title, achievedAt, and adminId are required", 400);
45-
// }
46-
47-
// const newAchievement = await achievementService.createAchievement({
48-
// title,
49-
// achievedAt,
50-
// imageUrl,
51-
// });
52-
53-
// res.status(201).json({
54-
// success: true,
55-
// data: newAchievement,
56-
// });
57-
// };
58-
47+
const imageUrl = await uploadImage(supabase, file, 'achievements');
48+
if (!imageUrl) {
49+
throw new ApiError('Image URL is missing', 400);
50+
}
5951

60-
export const createAchievement = async (req: Request, res: Response) => {
61-
const { title, achievedAt, imageUrl, memberIds } = req.body;
52+
const { title, description, achievedAt, memberIds, createdById } = req.body.achievementData;
6253

63-
if (!title || !achievedAt) {
64-
throw new ApiError("Title, achievedAt, and adminId are required", 400);
54+
if (!title || !description || !achievedAt || !createdById) {
55+
throw new ApiError('Title, description, achievedAt, and createdById are required', 400);
6556
}
6657

6758
if (!Array.isArray(memberIds)) {
68-
throw new ApiError("memberIds must be an array", 400);
59+
throw new ApiError('memberIds must be an array', 400);
6960
}
7061

7162
const achievement = await achievementService.createAchievement({
7263
title,
64+
description,
7365
achievedAt,
7466
imageUrl,
7567
memberIds,
68+
createdById,
7669
});
7770

7871
res.status(201).json({
@@ -82,67 +75,69 @@ export const createAchievement = async (req: Request, res: Response) => {
8275
};
8376

8477

85-
// export const addMemberToAchievement = async (req: Request, res: Response) => {
86-
// const achievementId = parseInt(req.params.achievementId);
87-
// const { memberId } = req.body;
88-
89-
// if (!achievementId || isNaN(achievementId)) {
90-
// throw new ApiError("Invalid achievement ID", 400);
91-
// }
92-
93-
// if (!memberId) {
94-
// throw new ApiError("Member ID is required", 400);
95-
// }
96-
97-
// const newLink = await achievementService.addMemberToAchievement({
98-
// achievementId,
99-
// memberId,
100-
// });
101-
102-
// res.status(201).json({
103-
// success: true,
104-
// data: newLink,
105-
// });
106-
// };
107-
108-
10978
export const updateAchievementById = async (req: Request, res: Response) => {
11079
const achievementId = parseInt(req.params.achievementId);
111-
11280
if (!achievementId || isNaN(achievementId)) {
11381
throw new ApiError("Invalid achievement ID", 400);
11482
}
11583

116-
const { title, achievedAt, imageUrl, memberIds } = req.body;
84+
const file = req.file;
85+
let imageUrl: string | undefined;
11786

118-
// if (!adminId) {
119-
// return next(new ApiError("Admin ID is required", 400));
120-
// }
87+
if (file) {
88+
imageUrl = await uploadImage(supabase, file, 'achievements');
89+
}
12190

91+
let achievementData = req.body.achievementData;
92+
if (typeof achievementData === 'string') {
93+
try {
94+
achievementData = JSON.parse(achievementData);
95+
} catch (e) {
96+
throw new ApiError("Invalid JSON in achievementData field", 400);
97+
}
98+
}
99+
100+
const { title, description, achievedAt, updatedById, memberIds } = achievementData;
101+
102+
if (!updatedById) {
103+
throw new ApiError("updatedById is required", 400);
104+
}
122105

123-
const existingAchievement = await achievementService.getAchievementById(achievementId);
106+
if (
107+
!title &&
108+
!description &&
109+
!achievedAt &&
110+
!imageUrl &&
111+
(!Array.isArray(memberIds) || memberIds.length === 0)
112+
) {
113+
throw new ApiError("At least one field must be provided for update", 400);
114+
}
124115

116+
const existingAchievement = await achievementService.getAchievementById(achievementId);
125117
if (!existingAchievement) {
126118
throw new ApiError("Achievement not found", 404);
127119
}
128120

129-
const updatedAchievement = await achievementService.updateAchievementById(achievementId, {
130-
title,
131-
achievedAt,
132-
imageUrl,
133-
});
121+
if (imageUrl) {
122+
achievementData.imageUrl = imageUrl;
123+
}
124+
125+
const updatedAchievement = await achievementService.updateAchievementById(
126+
achievementId,
127+
achievementData
128+
);
134129

135130
if (Array.isArray(memberIds) && memberIds.length > 0) {
136131
await achievementService.addMembersToAchievement(achievementId, memberIds);
137132
}
138133

139-
140134
res.status(200).json({
141135
success: true,
142136
data: updatedAchievement,
143137
});
144138
};
145139

140+
146141
export const deleteAchievementById = async (req: Request, res: Response) => {
147142
const achievementId = parseInt(req.params.achievementId);
148143

@@ -159,16 +154,16 @@ export const deleteAchievementById = async (req: Request, res: Response) => {
159154
};
160155

161156

162-
export const removeMemberFromAchievement = async (req: Request, res: Response, next: NextFunction) => {
157+
export const removeMemberFromAchievement = async (req: Request, res: Response) => {
163158
const achievementId = parseInt(req.params.achievementId);
164-
const { memberId } = req.body;
159+
const memberId = req.params.memberId;
165160

166161
if (!achievementId || isNaN(achievementId)) {
167-
return next(new ApiError("Invalid achievement ID", 400));
162+
throw new ApiError("Invalid achievement ID", 400);
168163
}
169164

170165
if (!memberId) {
171-
return next(new ApiError("Member ID is required", 400));
166+
throw new ApiError("Member ID is required", 400);
172167
}
173168

174169
await achievementService.removeMemberFromAchievement(achievementId, memberId);
@@ -178,3 +173,4 @@ export const removeMemberFromAchievement = async (req: Request, res: Response, n
178173
message: "Member removed from achievement successfully",
179174
});
180175
};
176+

src/routes/achievements.ts

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,122 @@ import express from 'express';
22
import * as acheivementsCtrl from '../controllers/achievement.controller';
33
import { supabase } from '../app';
44
import { Multer } from 'multer';
5-
import { NextFunction } from 'express-serve-static-core';
65
import { SupabaseClient } from '@supabase/supabase-js';
76

7+
import { Request, Response,NextFunction } from 'express';
8+
9+
export function parseCreateAchievementData(req: Request, res: Response, next: NextFunction) {
10+
if (req.body.achievementData) {
11+
try {
12+
req.body.achievementData = JSON.parse(req.body.achievementData);
13+
} catch (error) {
14+
return res.status(400).json({ message: 'Invalid JSON in achievementData field' });
15+
}
16+
}
17+
next();
18+
}
19+
820

921

1022
export default function acheivementsRouter(upload: Multer, supabase: SupabaseClient) {
1123
const router = express.Router();
1224

1325

14-
router.get('/', acheivementsCtrl.getAchievements);
26+
/**
27+
* @api {get} /achievements Get all achievements
28+
* @apiName getAchievements
29+
* @apiGroup Achievement
30+
*
31+
* @apiSuccess {Object[]} achievements List of achievements
32+
* @apiError (500) InternalServerError Failed to fetch achievements
33+
*/
34+
router.get('/', acheivementsCtrl.getAchievements);
35+
36+
37+
38+
/**
39+
* @api {get} /achievements/:achievementId Get achievement by ID
40+
* @apiName getAchievementById
41+
* @apiGroup Achievement
42+
*
43+
* @apiParam (Path Params) {Number} achievementId Achievement ID
44+
* @apiSuccess {Object} achievement Achievement object
45+
* @apiError (400) BadRequest Invalid ID
46+
* @apiError (404) NotFound Achievement not found
47+
*/
1548
router.get("/:achievementId", acheivementsCtrl.getAchievementById);
16-
router.post("/", acheivementsCtrl.createAchievement);
17-
router.patch("/:achievementId", acheivementsCtrl.updateAchievementById);
49+
50+
51+
/**
52+
* @api {post} /achievements Create a new achievement
53+
* @apiName createAchievement
54+
* @apiGroup Achievement
55+
*
56+
* @apiParam (FormData) {File} image Image file
57+
* @apiParam (FormData) {String} achievementData JSON string of achievement fields:
58+
* - title: string
59+
* - description: string
60+
* - achievedAt: Date
61+
* - createdById: string
62+
* - members: string[]
63+
*
64+
* @apiSuccess {Object} achievement Created achievement
65+
* @apiError (400) BadRequest Missing or invalid data
66+
* @apiError (500) InternalServerError Server error
67+
*/
68+
router.post("/", upload.single('image') , parseCreateAchievementData ,acheivementsCtrl.createAchievement);
69+
70+
71+
/**
72+
* @api {patch} /achievements/:achievementId Update an achievement
73+
* @apiName updateAchievement
74+
* @apiGroup Achievement
75+
*
76+
* @apiParam (Path Params) {Number} achievementId Achievement ID
77+
* @apiParam (FormData) {File} [image] Optional new image
78+
* @apiParam (FormData) {String} achievementData JSON string of updated fields:
79+
* - title?: string
80+
* - description?: string
81+
* - achievedAt?: Date
82+
* - updatedById: string
83+
* - members?: string[]
84+
*
85+
* @apiSuccess {Object} achievement Updated achievement
86+
* @apiError (400) BadRequest Missing or invalid data
87+
* @apiError (403) Forbidden Not allowed to update
88+
* @apiError (404) NotFound Achievement not found
89+
* @apiError (500) InternalServerError Server error
90+
*/
91+
router.patch("/:achievementId",upload.single('image') , parseCreateAchievementData , acheivementsCtrl.updateAchievementById);
92+
93+
94+
/**
95+
* @api {delete} /achievements/:achievementId Delete an achievement
96+
* @apiName deleteAchievement
97+
* @apiGroup Achievement
98+
*
99+
* @apiParam (Path Params) {Number} achievementId Achievement ID
100+
*
101+
* @apiSuccess {String} message Deletion confirmation
102+
* @apiError (400) BadRequest Invalid ID
103+
* @apiError (404) NotFound Achievement not found
104+
* @apiError (500) InternalServerError Server error
105+
*/
18106
router.delete("/:achievementId", acheivementsCtrl.deleteAchievementById);
19-
// router.post("/:achievementId/members", acheivementsCtrl.addMemberToAchievement); // add single member
20-
router.delete("/:achievementId/members", acheivementsCtrl.removeMemberFromAchievement);
21107

108+
/**
109+
* @api {delete} /achievements/:achievementId/members/:memberId Remove a member from achievement
110+
* @apiName removeMemberFromAchievement
111+
* @apiGroup Achievement
112+
*
113+
* @apiParam (Path Params) {Number} achievementId Achievement ID
114+
* @apiParam (Path Params) {UUID} memberId Member ID
115+
*
116+
* @apiSuccess {String} message Success message
117+
* @apiError (400) BadRequest Missing or invalid parameters
118+
* @apiError (500) InternalServerError Server error
119+
*/
120+
router.delete("/:achievementId/members/:memberId", acheivementsCtrl.removeMemberFromAchievement);
22121

23122
return router
24123
}

0 commit comments

Comments
 (0)