Skip to content

Commit 2b873d7

Browse files
authored
Merge pull request #63 from adgator101/develop
Integrate Pagination Support for Projects Route
2 parents 11fe72f + de3be89 commit 2b873d7

7 files changed

Lines changed: 246 additions & 204 deletions

File tree

Lines changed: 152 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Request, Response } from 'express';
2-
import { ErrorResponse, SuccessResponse } from '../dtos/index.js';
2+
import { ErrorResponse, PaginationResponse, SuccessResponse } from '../dtos/index.js';
33
import { HTTP } from '@/utils/constants.js';
44
import { projectServices } from '@/services/project.service.js';
55
import type { CreateProjectInput, UpdateProjectInput } from '@/lib/zod/project.schema.js';
@@ -8,140 +8,157 @@ import { tagServices } from '@/services/tag.service.js';
88
import { contributorServices } from '@/services/contributor.service.js';
99

1010
class ProjectController {
11-
async getAllProjects(req: Request, res: Response) {
12-
try {
13-
const projectResult = await projectServices.getAllProjects();
14-
15-
if (!projectResult.success || !projectResult.data) {
16-
return res
17-
.status(HTTP.BAD_REQUEST)
18-
.json(ErrorResponse(HTTP.BAD_REQUEST, projectResult.error));
19-
}
20-
21-
res.status(HTTP.OK).json(SuccessResponse(HTTP.OK, 'Fetched Sucessfully', projectResult));
22-
} catch (error) {
23-
console.error('Error in getAllProjects controller:', error);
24-
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
25-
}
26-
}
27-
28-
async addProject(req: Request, res: Response) {
29-
try {
30-
const { name, githubLink, demoLink, description, techStacks, tagIds }: CreateProjectInput =
31-
req.body;
32-
const thumbnail = req.file;
33-
let repoName = getRepoNameFromGithubUrl(githubLink);
34-
35-
const addProjectResult = await projectServices.createProject(
36-
{
37-
name,
38-
githubLink,
39-
demoLink,
40-
description,
41-
techStacks,
42-
tagIds,
43-
},
44-
thumbnail
45-
);
46-
47-
if (!addProjectResult.success || !addProjectResult.data) {
48-
return res
49-
.status(HTTP.BAD_REQUEST)
50-
.json(
51-
ErrorResponse(
52-
HTTP.BAD_REQUEST,
53-
typeof addProjectResult.error === 'string'
54-
? addProjectResult.error
55-
: 'Failed to add project'
56-
)
57-
);
58-
}
59-
60-
// If tagIds are provided, associate them with the project
61-
const projectId = addProjectResult.data.id;
62-
63-
if (tagIds && tagIds.length > 0) {
64-
const tagAssociationResult = await tagServices.associateTagToProject(projectId, tagIds);
65-
if (!tagAssociationResult.success || !tagAssociationResult.data) {
66-
console.warn(
67-
'Some tags failed to associate with the project:',
68-
tagAssociationResult.error
69-
);
70-
return res
71-
.status(HTTP.BAD_REQUEST)
72-
.json(ErrorResponse(HTTP.BAD_REQUEST, 'Failed to associate tags'));
73-
}
74-
}
75-
76-
// If repoName is provided add contributors from that repo
77-
if (repoName && addProjectResult.data.id) {
78-
const contributorResult = await contributorServices.addGithubContributorsToProject(
79-
repoName,
80-
addProjectResult.data.id
81-
);
82-
if (!contributorResult.success) {
83-
console.warn('Some contributors failed to process:', contributorResult.error);
84-
}
85-
}
86-
87-
return res
88-
.status(HTTP.OK)
89-
.json(SuccessResponse(HTTP.OK, 'Project added successfully', addProjectResult.data));
90-
} catch (error) {
91-
console.error('Error in addProject controller:', error);
92-
return res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
93-
}
94-
}
95-
96-
async updateProject(req: Request, res: Response) {
97-
try {
98-
const projectId = req.params.id;
99-
const updates: UpdateProjectInput = req.body;
100-
const thumbnail = req.file;
101-
102-
const updateProjectResult = await projectServices.updateProject(
103-
projectId,
104-
updates,
105-
thumbnail
106-
);
107-
108-
if (!updateProjectResult.success || !updateProjectResult.data) {
109-
return res
110-
.status(HTTP.BAD_REQUEST)
111-
.json(
112-
ErrorResponse(HTTP.BAD_REQUEST, updateProjectResult.error || 'Failed to update project')
113-
);
114-
}
115-
return res
116-
.status(HTTP.OK)
117-
.json(SuccessResponse(HTTP.OK, 'Project updated successfully', updateProjectResult.data));
118-
} catch (error) {
119-
console.error('Error in updating project:', error);
120-
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
121-
}
122-
}
123-
124-
async deleteProject(req: Request, res: Response) {
125-
try {
126-
const projectId = req.params.id;
127-
128-
const deleteProjectResult = await projectServices.deleteProject(projectId);
129-
130-
if (!deleteProjectResult.success) {
131-
return res
132-
.status(HTTP.BAD_REQUEST)
133-
.json(
134-
ErrorResponse(HTTP.BAD_REQUEST, deleteProjectResult.error || 'Failed to delete project')
135-
);
136-
}
137-
return res
138-
.status(HTTP.OK)
139-
.json(SuccessResponse(HTTP.OK, 'Project deleted successfully', null));
140-
} catch (error) {
141-
console.error('Error in deleting project:', error);
142-
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
143-
}
144-
}
11+
async getAllProjects(req: Request, res: Response) {
12+
try {
13+
const query = (req as any).validatedQuery;
14+
const page = query.page;
15+
const limit = query.limit;
16+
const skip = (page - 1) * limit;
17+
18+
const projectResult = await projectServices.getAllProjects({ skip, limit });
19+
20+
if (!projectResult.success || !projectResult.data) {
21+
return res
22+
.status(HTTP.BAD_REQUEST)
23+
.json(ErrorResponse(HTTP.BAD_REQUEST, projectResult.error));
24+
}
25+
26+
const { mappedProjects, total } = projectResult.data;
27+
res
28+
.status(HTTP.OK)
29+
.json(
30+
PaginationResponse(
31+
HTTP.OK,
32+
'Projects fetched successfully',
33+
mappedProjects,
34+
total,
35+
page,
36+
limit
37+
)
38+
);
39+
} catch (error) {
40+
console.error('Error in getAllProjects controller:', error);
41+
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
42+
}
43+
}
44+
45+
async addProject(req: Request, res: Response) {
46+
try {
47+
const { name, githubLink, demoLink, description, techStacks, tagIds }: CreateProjectInput =
48+
req.body;
49+
const thumbnail = req.file;
50+
let repoName = getRepoNameFromGithubUrl(githubLink);
51+
52+
const addProjectResult = await projectServices.createProject(
53+
{
54+
name,
55+
githubLink,
56+
demoLink,
57+
description,
58+
techStacks,
59+
tagIds,
60+
},
61+
thumbnail
62+
);
63+
64+
if (!addProjectResult.success || !addProjectResult.data) {
65+
return res
66+
.status(HTTP.BAD_REQUEST)
67+
.json(
68+
ErrorResponse(
69+
HTTP.BAD_REQUEST,
70+
typeof addProjectResult.error === 'string'
71+
? addProjectResult.error
72+
: 'Failed to add project'
73+
)
74+
);
75+
}
76+
77+
// If tagIds are provided, associate them with the project
78+
const projectId = addProjectResult.data.id;
79+
80+
if (tagIds && tagIds.length > 0) {
81+
const tagAssociationResult = await tagServices.associateTagToProject(projectId, tagIds);
82+
if (!tagAssociationResult.success || !tagAssociationResult.data) {
83+
console.warn(
84+
'Some tags failed to associate with the project:',
85+
tagAssociationResult.error
86+
);
87+
return res
88+
.status(HTTP.BAD_REQUEST)
89+
.json(ErrorResponse(HTTP.BAD_REQUEST, 'Failed to associate tags'));
90+
}
91+
}
92+
93+
// If repoName is provided add contributors from that repo
94+
if (repoName && addProjectResult.data.id) {
95+
const contributorResult = await contributorServices.addGithubContributorsToProject(
96+
repoName,
97+
addProjectResult.data.id
98+
);
99+
if (!contributorResult.success) {
100+
console.warn('Some contributors failed to process:', contributorResult.error);
101+
}
102+
}
103+
104+
return res
105+
.status(HTTP.OK)
106+
.json(SuccessResponse(HTTP.OK, 'Project added successfully', addProjectResult.data));
107+
} catch (error) {
108+
console.error('Error in addProject controller:', error);
109+
return res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
110+
}
111+
}
112+
113+
async updateProject(req: Request, res: Response) {
114+
try {
115+
const projectId = req.params.id;
116+
const updates: UpdateProjectInput = req.body;
117+
const thumbnail = req.file;
118+
119+
const updateProjectResult = await projectServices.updateProject(
120+
projectId,
121+
updates,
122+
thumbnail
123+
);
124+
125+
if (!updateProjectResult.success || !updateProjectResult.data) {
126+
return res
127+
.status(HTTP.BAD_REQUEST)
128+
.json(
129+
ErrorResponse(HTTP.BAD_REQUEST, updateProjectResult.error || 'Failed to update project')
130+
);
131+
}
132+
return res
133+
.status(HTTP.OK)
134+
.json(SuccessResponse(HTTP.OK, 'Project updated successfully', updateProjectResult.data));
135+
} catch (error) {
136+
console.error('Error in updating project:', error);
137+
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
138+
}
139+
}
140+
141+
async deleteProject(req: Request, res: Response) {
142+
try {
143+
const projectId = req.params.id;
144+
145+
const deleteProjectResult = await projectServices.deleteProject(projectId);
146+
147+
if (!deleteProjectResult.success) {
148+
return res
149+
.status(HTTP.BAD_REQUEST)
150+
.json(
151+
ErrorResponse(HTTP.BAD_REQUEST, deleteProjectResult.error || 'Failed to delete project')
152+
);
153+
}
154+
return res
155+
.status(HTTP.OK)
156+
.json(SuccessResponse(HTTP.OK, 'Project deleted successfully', null));
157+
} catch (error) {
158+
console.error('Error in deleting project:', error);
159+
res.status(HTTP.INTERNAL).json(ErrorResponse(HTTP.INTERNAL, 'Internal Server Error'));
160+
}
161+
}
145162
}
146163

147164
export const projectController = new ProjectController();

src/dtos/pagination.response.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
interface PaginationMeta {
22
total: number;
33
page: number;
4-
limit: number;
4+
// limit: number;
55
totalPages: number;
6+
hasNext: boolean;
67
}
78

89
interface PaginationResponseData<T = any> {
@@ -22,7 +23,7 @@ export default function PaginationResponse<T = any>(
2223
limit: number
2324
): PaginationResponseData<T> {
2425
const totalPages = Math.ceil(total / limit);
25-
26+
const hasNext = page < totalPages;
2627
return {
2728
success: true,
2829
message,
@@ -31,8 +32,9 @@ export default function PaginationResponse<T = any>(
3132
pagination: {
3233
total,
3334
page,
34-
limit,
35+
// limit,
3536
totalPages,
37+
hasNext: hasNext,
3638
},
3739
};
3840
}

src/lib/zod/member.schema.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,7 @@ export const memberParamsSchema = z
3434
})
3535
.strict();
3636

37-
// Get Members Query Schema
38-
export const getMembersQuerySchema = z
39-
.object({
40-
page: z
41-
.string()
42-
.regex(/^\d+$/, "Page must be a positive number")
43-
.transform(Number)
44-
.refine((val) => val > 0, "Page must be greater than 0")
45-
.optional()
46-
.default(1),
47-
48-
limit: z
49-
.string()
50-
.regex(/^\d+$/, "Limit must be a positive number")
51-
.transform(Number)
52-
.refine((val) => val > 0 && val <= 100, "Limit must be between 1 and 100")
53-
.optional()
54-
.default(10),
55-
})
56-
.strict();
57-
5837
// TypeScript Types
5938
export type CreateMemberInput = z.infer<typeof createMemberSchema>;
6039
export type UpdateMemberInput = z.infer<typeof updateMemberSchema>;
61-
export type MemberParamsInput = z.infer<typeof memberParamsSchema>;
62-
export type GetMembersQueryInput = z.infer<typeof getMembersQuerySchema>;
40+
export type MemberParamsInput = z.infer<typeof memberParamsSchema>;

src/lib/zod/pagination.schema.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { z } from 'zod';
2+
3+
export const paginationQuerySchema = z
4+
.object({
5+
page: z
6+
.string()
7+
.regex(/^\d+$/, 'Page must be a positive number')
8+
.transform(Number)
9+
.refine((val) => val > 0, 'Page must be greater than 0')
10+
.optional()
11+
.default(1),
12+
13+
limit: z
14+
.string()
15+
.regex(/^\d+$/, 'Limit must be a positive number')
16+
.transform(Number)
17+
.refine((val) => val > 0 && val <= 100, 'Limit must be between 1 and 100')
18+
.optional()
19+
.default(10),
20+
})
21+
.strict();
22+
23+
export type PaginationQueryInput = z.infer<typeof paginationQuerySchema>;

0 commit comments

Comments
 (0)