Skip to content

Commit f81e6c0

Browse files
committed
finalize reports page
2 parents abc1420 + 3f7bb36 commit f81e6c0

File tree

123 files changed

+4776
-27083
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+4776
-27083
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ Backend: http://localhost:5000/graphql
3939

4040
## Database Interactions
4141
Apply / migrate changes in prisma.schema to the database:
42-
1. Change the DATABASE_URL in the backend .env file to: postgresql://postgres:postgres@**localhost**:5432/mp
43-
2. In your terminal, run `npx prisma migrate dev` in the backend folder and follow the prompts
44-
3. Don’t forget to reset DATABASE_URL back to postgresql://postgres:postgres@**mp_db**:5432/mp
42+
1. First ensure your `mp_db` container is running
43+
2. Change the DATABASE_URL in the backend .env file to: postgresql://postgres:postgres@**localhost**:5432/mp
44+
3. In your terminal, run `npx prisma migrate dev` in the backend folder and follow the prompts
45+
4. Don’t forget to reset DATABASE_URL back to postgresql://postgres:postgres@**mp_db**:5432/mp
4546

4647
Common database commands:
4748
```bash

backend/crons/scripts/assignRequiredTasks.ts

Lines changed: 4 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,200 +1,14 @@
1-
import { TaskType, DayPreference, TimePreference } from "@prisma/client";
2-
import { addDays, endOfDay, set, startOfDay, startOfWeek } from "date-fns";
1+
import { Task, TaskType } from "@prisma/client";
32
import db from "../../prisma";
4-
import { orderedDays } from "../../constants/days";
3+
import { assignTasksToAllParticipants } from "../../utils/taskUtils";
54

65
// assigns all required tasks to each participant for the current week
76
async function assignRequiredTasks() {
87
try {
9-
const participants = await db.participant.findMany({
10-
where: {
11-
OR: [{ departure: null }, { departure: { gt: endOfDay(new Date()) } }],
12-
},
13-
select: { pid: true },
14-
});
15-
16-
const requiredTasks = await db.task.findMany({
8+
const requiredTasks: Task[] = await db.task.findMany({
179
where: { type: TaskType.REQUIRED },
1810
});
19-
20-
await Promise.all(
21-
participants.map(async (participant) => {
22-
await Promise.all(
23-
requiredTasks.map(async (requiredTask) => {
24-
if (
25-
requiredTask.day_preference ===
26-
DayPreference.PARTICIPANT_PREFERENCE ||
27-
requiredTask.time_preference ===
28-
TimePreference.PARTICIPANT_PREFERENCE
29-
) {
30-
throw new Error("required task must be strictly defined");
31-
}
32-
33-
if (requiredTask.day_preference === DayPreference.DAILY) {
34-
await Promise.all(
35-
orderedDays.map(async (day) => {
36-
const baseDate = addDays(
37-
startOfWeek(new Date()),
38-
orderedDays.indexOf(day)
39-
);
40-
if (
41-
requiredTask.time_preference === TimePreference.SPECIFIC
42-
) {
43-
if (
44-
requiredTask.start_time === null ||
45-
requiredTask.end_time == null
46-
) {
47-
throw new Error(
48-
"required task is missing time information"
49-
);
50-
}
51-
52-
const startDate = set(baseDate, {
53-
hours: requiredTask.start_time.getHours(),
54-
minutes: requiredTask.start_time.getMinutes(),
55-
seconds: requiredTask.start_time.getSeconds(),
56-
milliseconds: requiredTask.start_time.getMilliseconds(),
57-
});
58-
59-
const endDate = set(baseDate, {
60-
hours: requiredTask.end_time.getHours(),
61-
minutes: requiredTask.end_time.getMinutes(),
62-
seconds: requiredTask.end_time.getSeconds(),
63-
milliseconds: requiredTask.end_time.getMilliseconds(),
64-
});
65-
66-
await db.assignedTask.create({
67-
data: {
68-
pid: participant.pid,
69-
name: requiredTask.name,
70-
type: requiredTask.type,
71-
value: requiredTask.value,
72-
penalty: requiredTask.penalty,
73-
comment: requiredTask.comment,
74-
start_date: startDate,
75-
end_date: endDate,
76-
},
77-
});
78-
} else if (
79-
requiredTask.time_preference === TimePreference.ANYTIME
80-
) {
81-
const startDate = startOfDay(baseDate);
82-
const endDate = addDays(startDate, 1);
83-
84-
await db.assignedTask.create({
85-
data: {
86-
pid: participant.pid,
87-
name: requiredTask.name,
88-
type: requiredTask.type,
89-
value: requiredTask.value,
90-
penalty: requiredTask.penalty,
91-
comment: requiredTask.comment,
92-
start_date: startDate,
93-
end_date: endDate,
94-
},
95-
});
96-
}
97-
})
98-
);
99-
} else if (
100-
requiredTask.day_preference === DayPreference.EVERY_SELECTED_DAYS
101-
) {
102-
await Promise.all(
103-
requiredTask.days.map(async (day) => {
104-
const baseDate = addDays(
105-
startOfWeek(new Date()),
106-
orderedDays.indexOf(day)
107-
);
108-
if (
109-
requiredTask.time_preference === TimePreference.SPECIFIC
110-
) {
111-
if (
112-
requiredTask.start_time === null ||
113-
requiredTask.end_time == null
114-
) {
115-
throw new Error(
116-
"required task is missing time information"
117-
);
118-
}
119-
120-
const startDate = set(baseDate, {
121-
hours: requiredTask.start_time.getHours(),
122-
minutes: requiredTask.start_time.getMinutes(),
123-
seconds: requiredTask.start_time.getSeconds(),
124-
milliseconds: requiredTask.start_time.getMilliseconds(),
125-
});
126-
127-
const endDate = set(baseDate, {
128-
hours: requiredTask.end_time.getHours(),
129-
minutes: requiredTask.end_time.getMinutes(),
130-
seconds: requiredTask.end_time.getSeconds(),
131-
milliseconds: requiredTask.end_time.getMilliseconds(),
132-
});
133-
134-
await db.assignedTask.create({
135-
data: {
136-
pid: participant.pid,
137-
name: requiredTask.name,
138-
type: requiredTask.type,
139-
value: requiredTask.value,
140-
penalty: requiredTask.penalty,
141-
comment: requiredTask.comment,
142-
start_date: startDate,
143-
end_date: endDate,
144-
},
145-
});
146-
} else if (
147-
requiredTask.time_preference === TimePreference.ANYTIME
148-
) {
149-
const startDate = startOfDay(baseDate);
150-
const endDate = addDays(startDate, 1);
151-
152-
await db.assignedTask.create({
153-
data: {
154-
pid: participant.pid,
155-
name: requiredTask.name,
156-
type: requiredTask.type,
157-
value: requiredTask.value,
158-
penalty: requiredTask.penalty,
159-
comment: requiredTask.comment,
160-
start_date: startDate,
161-
end_date: endDate,
162-
},
163-
});
164-
}
165-
})
166-
);
167-
} else if (
168-
requiredTask.day_preference === DayPreference.DAY_RANGE
169-
) {
170-
if (requiredTask.days.length !== 2)
171-
throw new Error("day range must contain exactly two elements");
172-
const startDate = addDays(
173-
startOfWeek(new Date()),
174-
orderedDays.indexOf(requiredTask.days[0])
175-
);
176-
const endDate = addDays(
177-
startOfWeek(new Date()),
178-
orderedDays.indexOf(requiredTask.days[1]) + 1
179-
);
180-
181-
await db.assignedTask.create({
182-
data: {
183-
pid: participant.pid,
184-
name: requiredTask.name,
185-
type: requiredTask.type,
186-
value: requiredTask.value,
187-
penalty: requiredTask.penalty,
188-
comment: requiredTask.comment,
189-
start_date: startDate,
190-
end_date: endDate,
191-
},
192-
});
193-
}
194-
})
195-
);
196-
})
197-
);
11+
assignTasksToAllParticipants(requiredTasks);
19812
console.log("successfully assigned required tasks to participants");
19913
} catch (err) {
20014
console.error(err);

backend/crons/scripts/sendMonthlyReport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ async function sendMonthlyReport() {
2424
})
2525
);
2626

27+
// if necessary, we can clear relevant data that is already recorded in the generated report
2728
console.log("successfully sent monthly report to recipients");
2829
} catch (err) {
2930
console.error(err);

backend/gql/middleware.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ export default function getMiddleware() {
114114
ROLES.RELIEF,
115115
ROLES.PARTICIPANT,
116116
]),
117+
getBadgeLevelProgress: verifyRole([
118+
ROLES.ADMIN,
119+
ROLES.RELIEF,
120+
ROLES.PARTICIPANT,
121+
]),
117122
},
118123
Mutation: {
119124
createNote: verifyRole([ROLES.ADMIN, ROLES.RELIEF]),

backend/gql/resolvers/assignedTaskResolver.ts

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { updateBadgeLevelProgress } from "../../utils/badgeUtils";
66
import {
77
PERFECT_SCORE_OPTIONAL,
88
PERFECT_SCORE_REQUIRED,
9+
JACK_OF_ALL_TRADES,
10+
FIRST_GOAL,
11+
INDIVIDUAL_GOAL,
912
} from "../../constants/systemBadges";
1013

1114
const assignedTaskResolver = {
@@ -108,6 +111,7 @@ const assignedTaskResolver = {
108111
_parent: undefined,
109112
{
110113
pid,
114+
tid,
111115
name,
112116
type,
113117
value,
@@ -117,6 +121,7 @@ const assignedTaskResolver = {
117121
comment,
118122
}: {
119123
pid: number;
124+
tid: number;
120125
name: string;
121126
type: TaskType;
122127
value: number;
@@ -129,6 +134,7 @@ const assignedTaskResolver = {
129134
return db.assignedTask.create({
130135
data: {
131136
pid,
137+
tid,
132138
name,
133139
type,
134140
value,
@@ -207,17 +213,19 @@ const assignedTaskResolver = {
207213
);
208214

209215
if (assignedTask.type === TaskType.REQUIRED) {
210-
const weeklyRequiredTasks = await db.assignedTask.findMany({
211-
where: {
212-
pid: assignedTask.pid,
213-
status: { not: TaskStatus.COMPLETE },
214-
start_date: { lte: endOfWeek(new Date()) },
215-
end_date: { gte: startOfWeek(new Date()) },
216-
type: TaskType.REQUIRED,
217-
},
218-
});
216+
const weeklyRequiredTasksNotComplete = await db.assignedTask.findMany(
217+
{
218+
where: {
219+
pid: assignedTask.pid,
220+
status: { not: TaskStatus.COMPLETE },
221+
start_date: { lte: endOfWeek(new Date()) },
222+
end_date: { gte: startOfWeek(new Date()) },
223+
type: TaskType.REQUIRED,
224+
},
225+
}
226+
);
219227

220-
if (weeklyRequiredTasks.length === 0) {
228+
if (weeklyRequiredTasksNotComplete.length === 1) {
221229
await updateBadgeLevelProgress(
222230
PERFECT_SCORE_REQUIRED,
223231
assignedTask.pid,
@@ -242,25 +250,46 @@ const assignedTaskResolver = {
242250
1
243251
);
244252
}
245-
}
246-
247-
// TODO (victor): add jack of all trades, first goal, individual goal badge logic
253+
} else if (assignedTask.type === TaskType.INDIVIDUAL_GOAL) {
254+
const individualGoalTasksNotComplete = await db.assignedTask.findMany(
255+
{
256+
where: {
257+
pid: assignedTask.pid,
258+
type: TaskType.INDIVIDUAL_GOAL,
259+
status: { not: TaskStatus.COMPLETE },
260+
start_date: { lte: endOfWeek(new Date()) },
261+
end_date: { gte: startOfWeek(new Date()) },
262+
},
263+
}
264+
);
248265

249-
// Jack of All Trades:
250-
// If the task that's just been completed has not been completed before, update badge level progress for the JACK_OF_ALL_TRADES badge by 1
251-
// For this badge, you might need to add tid to the AssignedTask table (not as a FK referencing the Task table, just as an attribute, since if the referenced Task is deleted we still want to preserve the state of the AssignedTask)
252-
// Places you might need to update with this new property include (types/models.ts, types/resolvers.ts, prisma/schema.prisma, createAssignedTask resolver)
253-
// To process new schema changes, you'll need to run the following commands:
254-
// npx prisma migrate reset
255-
// npx prisma migrate dev
256-
// npx @snaplet/seed sync
257-
// Also ensure that the mp_db container is running and you've set DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mp after the container is setup
266+
if (individualGoalTasksNotComplete.length === 1) {
267+
await updateBadgeLevelProgress(
268+
INDIVIDUAL_GOAL,
269+
assignedTask.pid,
270+
1
271+
);
272+
}
258273

259-
// First Goal:
260-
// No condition needs to be checked, just call updateBadgeLevelProgress for the FIRST_GOAL badge with inc = 1
274+
await updateBadgeLevelProgress(FIRST_GOAL, assignedTask.pid, 1);
275+
}
261276

262-
// Individual Goal:
263-
// Check if all assigned tasks of type INDIVIDUAL_GOAL have been completed for the week, if so, update badge level progress for the INDIVIDUAL_GOAL badge by 1
277+
const hasPreviouslyCompleted = await db.assignedTask
278+
.findFirst({
279+
where: {
280+
tid: assignedTask.tid,
281+
status: TaskStatus.COMPLETE,
282+
pid: assignedTask.pid,
283+
},
284+
})
285+
.then((task) => task !== null);
286+
if (!hasPreviouslyCompleted) {
287+
await updateBadgeLevelProgress(
288+
JACK_OF_ALL_TRADES,
289+
assignedTask.pid,
290+
1
291+
);
292+
}
264293
}
265294

266295
return db.assignedTask.update({
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { BadgeLevelProgress } from "@prisma/client";
2+
import db from "../../prisma";
3+
4+
const badgeLevelProgressResolver = {
5+
Query: {
6+
getBadgeLevelProgress: async (
7+
_parent: undefined,
8+
{
9+
pid,
10+
}: {
11+
pid: number;
12+
}
13+
): Promise<BadgeLevelProgress[]> => {
14+
return db.badgeLevelProgress.findMany({
15+
where: { pid, badge_level: { system_badge: { is_active: true } } },
16+
include: {
17+
badge_level: {
18+
include: {
19+
system_badge: true,
20+
},
21+
},
22+
},
23+
});
24+
},
25+
},
26+
};
27+
28+
export default badgeLevelProgressResolver;

0 commit comments

Comments
 (0)