Skip to content

Commit 906c143

Browse files
committed
fix: add hackatime project support
1 parent 9a1682a commit 906c143

File tree

11 files changed

+414
-4
lines changed

11 files changed

+414
-4
lines changed

owl-api/prisma/migrations/20250123120000_init_consolidated/migration.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ CREATE TABLE IF NOT EXISTS "projects" (
5454
"project_title" VARCHAR(30) NOT NULL,
5555
"project_type" "ProjectType" NOT NULL,
5656
"now_hackatime_hours" DOUBLE PRECISION,
57+
"now_hackatime_projects" TEXT[] DEFAULT '{}',
5758
"approved_hours" DOUBLE PRECISION,
5859
"description" VARCHAR(500),
5960
"screenshot_url" TEXT,

owl-api/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ model Project {
4747
description String? @db.VarChar(500)
4848
hoursJustification String? @map("hours_justification") @db.VarChar(500)
4949
nowHackatimeHours Float? @map("now_hackatime_hours")
50+
nowHackatimeProjects String[] @map("now_hackatime_projects")
5051
playableUrl String? @map("playable_url")
5152
projectTitle String @map("project_title") @db.VarChar(30)
5253
repoUrl String? @map("repo_url")

owl-api/src/admin/admin.controller.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,25 @@ export class AdminController {
4646
) {
4747
return this.adminService.unlockProject(id, req.user.userId);
4848
}
49+
50+
@Put('edit-requests/:id/approve')
51+
@UseGuards(RolesGuard)
52+
@Roles(Role.Admin)
53+
async approveEditRequest(
54+
@Param('id', ParseIntPipe) id: number,
55+
@Req() req: Request,
56+
) {
57+
return this.adminService.approveEditRequest(id, req.user.userId);
58+
}
59+
60+
@Put('edit-requests/:id/reject')
61+
@UseGuards(RolesGuard)
62+
@Roles(Role.Admin)
63+
async rejectEditRequest(
64+
@Param('id', ParseIntPipe) id: number,
65+
@Body() body: { reason: string },
66+
@Req() req: Request,
67+
) {
68+
return this.adminService.rejectEditRequest(id, body.reason, req.user.userId);
69+
}
4970
}

owl-api/src/admin/admin.service.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export class AdminService {
114114
repoUrl: submission.project.repoUrl,
115115
screenshotUrl: submission.project.screenshotUrl,
116116
nowHackatimeHours: submission.project.nowHackatimeHours,
117+
nowHackatimeProjects: submission.project.nowHackatimeProjects,
117118
},
118119
submission: {
119120
description: submission.description,
@@ -186,6 +187,7 @@ export class AdminService {
186187
repoUrl: true,
187188
screenshotUrl: true,
188189
nowHackatimeHours: true,
190+
nowHackatimeProjects: true,
189191
airtableRecId: true,
190192
isLocked: true,
191193
createdAt: true,
@@ -236,4 +238,156 @@ export class AdminService {
236238

237239
return updatedProject;
238240
}
241+
242+
async approveEditRequest(requestId: number, adminUserId: number) {
243+
const editRequest = await this.prisma.editRequest.findUnique({
244+
where: { requestId },
245+
include: {
246+
project: true,
247+
user: true,
248+
},
249+
});
250+
251+
if (!editRequest) {
252+
throw new NotFoundException('Edit request not found');
253+
}
254+
255+
if (editRequest.status !== 'pending') {
256+
throw new ForbiddenException('Edit request has already been processed');
257+
}
258+
259+
// Calculate hackatime hours if hackatime projects are being updated
260+
let calculatedHours = editRequest.project.nowHackatimeHours;
261+
if ((editRequest.requestedData as any).nowHackatimeProjects) {
262+
// For now, we'll set a placeholder value. In a real implementation,
263+
// you would fetch hours from the hackatime API based on project names
264+
calculatedHours = ((editRequest.requestedData as any).nowHackatimeProjects as string[]).length * 10; // Placeholder calculation
265+
}
266+
267+
// Update the project with the requested data
268+
const updateData: any = {};
269+
if ((editRequest.requestedData as any).projectTitle !== undefined) {
270+
updateData.projectTitle = (editRequest.requestedData as any).projectTitle;
271+
}
272+
if ((editRequest.requestedData as any).description !== undefined) {
273+
updateData.description = (editRequest.requestedData as any).description;
274+
}
275+
if ((editRequest.requestedData as any).playableUrl !== undefined) {
276+
updateData.playableUrl = (editRequest.requestedData as any).playableUrl;
277+
}
278+
if ((editRequest.requestedData as any).repoUrl !== undefined) {
279+
updateData.repoUrl = (editRequest.requestedData as any).repoUrl;
280+
}
281+
if ((editRequest.requestedData as any).screenshotUrl !== undefined) {
282+
updateData.screenshotUrl = (editRequest.requestedData as any).screenshotUrl;
283+
}
284+
if ((editRequest.requestedData as any).airtableRecId !== undefined) {
285+
updateData.airtableRecId = (editRequest.requestedData as any).airtableRecId;
286+
}
287+
if ((editRequest.requestedData as any).nowHackatimeProjects !== undefined) {
288+
updateData.nowHackatimeProjects = (editRequest.requestedData as any).nowHackatimeProjects;
289+
updateData.nowHackatimeHours = calculatedHours;
290+
}
291+
292+
// Update the project
293+
const updatedProject = await this.prisma.project.update({
294+
where: { projectId: editRequest.projectId },
295+
data: updateData,
296+
});
297+
298+
// Update the edit request status
299+
const updatedEditRequest = await this.prisma.editRequest.update({
300+
where: { requestId },
301+
data: {
302+
status: 'approved',
303+
reviewedBy: adminUserId,
304+
reviewedAt: new Date(),
305+
},
306+
include: {
307+
user: {
308+
select: {
309+
userId: true,
310+
firstName: true,
311+
lastName: true,
312+
email: true,
313+
},
314+
},
315+
project: {
316+
select: {
317+
projectId: true,
318+
projectTitle: true,
319+
projectType: true,
320+
},
321+
},
322+
reviewer: {
323+
select: {
324+
userId: true,
325+
firstName: true,
326+
lastName: true,
327+
email: true,
328+
},
329+
},
330+
},
331+
});
332+
333+
return {
334+
message: 'Edit request approved successfully.',
335+
editRequest: updatedEditRequest,
336+
project: updatedProject,
337+
};
338+
}
339+
340+
async rejectEditRequest(requestId: number, reason: string, adminUserId: number) {
341+
const editRequest = await this.prisma.editRequest.findUnique({
342+
where: { requestId },
343+
});
344+
345+
if (!editRequest) {
346+
throw new NotFoundException('Edit request not found');
347+
}
348+
349+
if (editRequest.status !== 'pending') {
350+
throw new ForbiddenException('Edit request has already been processed');
351+
}
352+
353+
const updatedEditRequest = await this.prisma.editRequest.update({
354+
where: { requestId },
355+
data: {
356+
status: 'rejected',
357+
reason,
358+
reviewedBy: adminUserId,
359+
reviewedAt: new Date(),
360+
},
361+
include: {
362+
user: {
363+
select: {
364+
userId: true,
365+
firstName: true,
366+
lastName: true,
367+
email: true,
368+
},
369+
},
370+
project: {
371+
select: {
372+
projectId: true,
373+
projectTitle: true,
374+
projectType: true,
375+
},
376+
},
377+
reviewer: {
378+
select: {
379+
userId: true,
380+
firstName: true,
381+
lastName: true,
382+
email: true,
383+
},
384+
},
385+
},
386+
});
387+
388+
return {
389+
message: 'Edit request rejected successfully.',
390+
editRequest: updatedEditRequest,
391+
};
392+
}
239393
}

owl-api/src/airtable/airtable.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class AirtableService {
2626
repoUrl: string;
2727
screenshotUrl: string;
2828
nowHackatimeHours: number;
29+
nowHackatimeProjects: string[];
2930
};
3031
submission: {
3132
description: string;
@@ -63,6 +64,7 @@ export class AirtableService {
6364
}
6465
],
6566
'Optional - Override Hours Spent': data.project.nowHackatimeHours,
67+
'Hackatime Projects': data.project.nowHackatimeProjects.join(', '),
6668
'Automation - First Submitted At': new Date().toISOString(),
6769
'Automation - Submit to Unified YSWS': true,
6870
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { IsArray, IsString, ArrayMinSize } from 'class-validator';
2+
3+
export class UpdateHackatimeProjectsDto {
4+
@IsArray()
5+
@ArrayMinSize(1)
6+
@IsString({ each: true })
7+
projectNames: string[];
8+
}

owl-api/src/projects/dto/update-project.dto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IsOptional, IsString, IsUrl, MaxLength } from 'class-validator';
1+
import { IsOptional, IsString, IsUrl, MaxLength, IsArray, ArrayMinSize } from 'class-validator';
22

33
export class UpdateProjectDto {
44
@IsString()
@@ -28,4 +28,10 @@ export class UpdateProjectDto {
2828
@IsString()
2929
@IsOptional()
3030
airtableRecId?: string;
31+
32+
@IsArray()
33+
@IsOptional()
34+
@ArrayMinSize(1)
35+
@IsString({ each: true })
36+
nowHackatimeProjects?: string[];
3137
}

owl-api/src/projects/projects.controller.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ProjectsService } from './projects.service';
44
import { CreateProjectDto } from './dto/create-project.dto';
55
import { UpdateProjectDto } from './dto/update-project.dto';
66
import { CreateSubmissionDto } from './dto/create-submission.dto';
7+
import { UpdateHackatimeProjectsDto } from './dto/update-hackatime-projects.dto';
78
import { AuthGuard } from '../auth/auth.guard';
89

910
@Controller('api/projects/auth')
@@ -56,4 +57,21 @@ export class ProjectsController {
5657
) {
5758
return this.projectsService.createEditRequest(id, updateProjectDto, req.user.userId);
5859
}
60+
61+
@Put(':id/hackatime-projects')
62+
async updateHackatimeProjects(
63+
@Param('id', ParseIntPipe) id: number,
64+
@Body() updateHackatimeProjectsDto: UpdateHackatimeProjectsDto,
65+
@Req() req: Request,
66+
) {
67+
return this.projectsService.updateHackatimeProjects(id, updateHackatimeProjectsDto, req.user.userId);
68+
}
69+
70+
@Get(':id/hackatime-projects')
71+
async getHackatimeProjects(
72+
@Param('id', ParseIntPipe) id: number,
73+
@Req() req: Request,
74+
) {
75+
return this.projectsService.getHackatimeProjects(id, req.user.userId);
76+
}
5977
}

0 commit comments

Comments
 (0)