Skip to content

Commit 618f928

Browse files
authored
Merge pull request #2682 from Northeastern-Electric-Racing/Weekly-Availability-Times
Weekly availability times
2 parents 5a0b281 + 79828b3 commit 618f928

18 files changed

Lines changed: 278 additions & 77 deletions

File tree

src/backend/src/prisma-query-args/user.query-args.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export type UserQueryArgs = ReturnType<typeof getUserQueryArgs>;
44

55
export type UserWithSettingsQueryArgs = ReturnType<typeof getUserWithSettingsQueryArgs>;
66

7+
export type UserScheduleSettingsQueryArgs = ReturnType<typeof getUserScheduleSettingsQueryArgs>;
8+
79
// DO NOT CALL ANY OTHER QUERY ARGS FROM HERE TO AVOID CIRCULAR DEPENDENCIES
810
export const getUserQueryArgs = (organizationId: string) =>
911
Prisma.validator<Prisma.UserDefaultArgs>()({
@@ -25,8 +27,16 @@ export const getUserWithSettingsQueryArgs = (organizationId: string) =>
2527
organizationId
2628
}
2729
},
28-
drScheduleSettings: true,
30+
drScheduleSettings: getUserScheduleSettingsQueryArgs(),
2931
userSettings: true,
3032
organizations: true
3133
}
3234
});
35+
36+
export const getUserScheduleSettingsQueryArgs = () => {
37+
return Prisma.validator<Prisma.Schedule_SettingsDefaultArgs>()({
38+
include: {
39+
availabilities: true
40+
}
41+
});
42+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `availability` on the `Schedule_Settings` table. All the data in the column will be lost.
5+
6+
*/
7+
8+
-- CreateTable
9+
CREATE TABLE "Availability" (
10+
"availabilityId" TEXT NOT NULL,
11+
"scheduleSettingsId" TEXT NOT NULL,
12+
"availability" INTEGER[],
13+
"dateSet" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
14+
15+
CONSTRAINT "Availability_pkey" PRIMARY KEY ("availabilityId")
16+
);
17+
18+
-- AddForeignKey
19+
ALTER TABLE "Availability" ADD CONSTRAINT "Availability_scheduleSettingsId_fkey" FOREIGN KEY ("scheduleSettingsId") REFERENCES "Schedule_Settings"("drScheduleSettingsId") ON DELETE RESTRICT ON UPDATE CASCADE;
20+
21+
/* Migrate all existing availabilities to new availability table */
22+
INSERT INTO "Availability" ("availabilityId", "scheduleSettingsId", "availability", "dateSet") SELECT gen_random_uuid(), "drScheduleSettingsId", "availability", CURRENT_TIMESTAMP FROM "Schedule_Settings";
23+
24+
-- AlterTable
25+
ALTER TABLE "Schedule_Settings" DROP COLUMN "availability";
26+
27+

src/backend/src/prisma/schema.prisma

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -736,14 +736,23 @@ model Design_Review {
736736
notificationSlackThreads Message_Info[]
737737
}
738738

739+
model Availability {
740+
availabilityId String @id @default(uuid())
741+
scheduleSettingsId String
742+
scheduleSettings Schedule_Settings @relation(fields: [scheduleSettingsId], references: [drScheduleSettingsId])
743+
744+
// Availibilies are integers between 0 and 83 from 10am - 10pm (Monday - Sunday) see meetingTime field in Design_Review
745+
availability Int[]
746+
dateSet DateTime @default(now())
747+
}
748+
739749
model Schedule_Settings {
740-
drScheduleSettingsId String @id @default(uuid())
750+
drScheduleSettingsId String @id @default(uuid())
741751
personalGmail String
742752
personalZoomLink String
743-
// Availibilies are integers between 0 and 83 from 10am - 10pm (Monday - Sunday) see meetingTime field in Design_Review
744-
availability Int[]
745-
User User @relation(fields: [userId], references: [userId])
746-
userId String @unique
753+
User User @relation(fields: [userId], references: [userId])
754+
userId String @unique
755+
availabilities Availability[]
747756
}
748757

749758
model Meeting {

src/backend/src/routes/users.routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ userRouter.post(
3434

3535
userRouter.post(
3636
'/schedule-settings/set',
37-
nonEmptyString(body('personalGmail')).isEmail(),
38-
nonEmptyString(body('personalZoomLink')).isURL(),
37+
body('personalGmail').isString(),
38+
body('personalZoomLink').isString(),
3939
body('availability').isArray(),
4040
intMinZero(body('availibility.*')),
4141
validateInputs,

src/backend/src/services/design-reviews.services.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import {
1010
AccessDeniedException,
1111
InvalidOrganizationException
1212
} from '../utils/errors.utils';
13-
import { getUsers, getPrismaQueryUserIds, userHasPermission, areUsersinList } from '../utils/users.utils';
13+
import {
14+
getUsers,
15+
getPrismaQueryUserIds,
16+
userHasPermission,
17+
areUsersinList,
18+
updateUserAvailability
19+
} from '../utils/users.utils';
1420
import { isUserOnDesignReview, validateMeetingTimes } from '../utils/design-reviews.utils';
1521
import { designReviewTransformer } from '../transformers/design-reviews.transformer';
1622
import {
@@ -23,6 +29,7 @@ import {
2329
import { getDesignReviewQueryArgs } from '../prisma-query-args/design-reviews.query-args';
2430
import { getWorkPackageQueryArgs } from '../prisma-query-args/work-packages.query-args';
2531
import { UserWithSettings } from '../utils/auth.utils';
32+
import { getUserScheduleSettingsQueryArgs } from '../prisma-query-args/user.query-args';
2633

2734
export default class DesignReviewsService {
2835
/**
@@ -363,25 +370,29 @@ export default class DesignReviewsService {
363370
if (!isUserOnDesignReview(submitter, designReviewTransformer(designReview)))
364371
throw new HttpException(400, 'Current user is not in the list of this design reviews members');
365372

366-
availability.forEach((time) => {
367-
if (time < 0 || time > 83) {
368-
throw new HttpException(400, 'Availability times have to be in range 0-83');
369-
}
370-
});
371-
372-
await prisma.schedule_Settings.upsert({
373+
let userSettings = await prisma.schedule_Settings.findUnique({
373374
where: { userId: submitter.userId },
374-
update: {
375-
availability
376-
},
377-
create: {
378-
userId: submitter.userId,
379-
personalGmail: '',
380-
personalZoomLink: '',
381-
availability
382-
}
375+
...getUserScheduleSettingsQueryArgs()
383376
});
384377

378+
if (!userSettings) {
379+
userSettings = await prisma.schedule_Settings.create({
380+
data: {
381+
userId: submitter.userId,
382+
availabilities: {
383+
create: {
384+
availability
385+
}
386+
},
387+
personalGmail: '',
388+
personalZoomLink: ''
389+
},
390+
...getUserScheduleSettingsQueryArgs()
391+
});
392+
}
393+
394+
await updateUserAvailability(availability, userSettings, submitter, designReview.dateScheduled);
395+
385396
// set submitter as confirmed if they're not already
386397
if (!designReview.confirmedMembers.map((user) => user.userId).includes(submitter.userId)) {
387398
const updatedDesignReview = await prisma.design_Review.update({

src/backend/src/services/users.services.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ import userSecureSettingsTransformer from '../transformers/user-secure-settings.
2727
import { validateUserIsPartOfFinanceTeam } from '../utils/reimbursement-requests.utils';
2828
import userScheduleSettingsTransformer from '../transformers/user-schedule-settings.transformer';
2929
import { userTransformer, userWithScheduleSettingsTransformer } from '../transformers/user.transformer';
30-
import { getUserRole } from '../utils/users.utils';
31-
import { getUserQueryArgs, getUserWithSettingsQueryArgs } from '../prisma-query-args/user.query-args';
30+
import { getUserRole, updateUserAvailability } from '../utils/users.utils';
31+
import {
32+
getUserQueryArgs,
33+
getUserScheduleSettingsQueryArgs,
34+
getUserWithSettingsQueryArgs
35+
} from '../prisma-query-args/user.query-args';
3236
import { getAuthUserQueryArgs } from '../prisma-query-args/auth-user.query-args';
3337
import authenticatedUserTransformer from '../transformers/auth-user.transformer';
3438

@@ -44,7 +48,7 @@ export default class UsersService {
4448
include: {
4549
roles: true,
4650
userSettings: true,
47-
drScheduleSettings: true,
51+
drScheduleSettings: getUserScheduleSettingsQueryArgs(),
4852
organizations: true
4953
}
5054
});
@@ -480,7 +484,8 @@ export default class UsersService {
480484
static async getUserScheduleSettings(userId: string, submitter: PrismaUser): Promise<UserScheduleSettings> {
481485
if (submitter.userId !== userId) throw new AccessDeniedException('You can only access your own schedule settings');
482486
const scheduleSettings = await prisma.schedule_Settings.findUnique({
483-
where: { userId }
487+
where: { userId },
488+
...getUserScheduleSettingsQueryArgs()
484489
});
485490
if (!scheduleSettings) throw new HttpException(404, 'User Schedule Settings Not Found');
486491

@@ -501,28 +506,32 @@ export default class UsersService {
501506
personalZoomLink: string,
502507
availability: number[]
503508
): Promise<UserScheduleSettings> {
504-
const existingUser = await prisma.schedule_Settings.findFirst({
505-
where: { personalGmail, userId: { not: user.userId } } // excludes the current user from check
506-
});
509+
if (personalGmail !== '') {
510+
const existingUser = await prisma.schedule_Settings.findFirst({
511+
where: { personalGmail, userId: { not: user.userId } } // excludes the current user from check
512+
});
507513

508-
if (existingUser) {
509-
throw new HttpException(400, 'Email already in use');
514+
if (existingUser) {
515+
throw new HttpException(400, 'Email already in use');
516+
}
510517
}
511518

512519
const newUserScheduleSettings = await prisma.schedule_Settings.upsert({
513520
where: { userId: user.userId },
514521
update: {
515522
personalGmail,
516-
personalZoomLink,
517-
availability
523+
personalZoomLink
518524
},
519525
create: {
520526
userId: user.userId,
521527
personalGmail,
522-
personalZoomLink,
523-
availability
524-
}
528+
personalZoomLink
529+
},
530+
...getUserScheduleSettingsQueryArgs()
525531
});
532+
533+
await updateUserAvailability(availability, newUserScheduleSettings, user, new Date());
534+
526535
return userScheduleSettingsTransformer(newUserScheduleSettings);
527536
}
528537
}

src/backend/src/transformers/user-schedule-settings.transformer.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Prisma } from '@prisma/client';
22
import { UserScheduleSettings } from 'shared';
3+
import { UserScheduleSettingsQueryArgs } from '../prisma-query-args/user.query-args';
34

4-
const userScheduleSettingsTransformer = (settings: Prisma.Schedule_SettingsGetPayload<null>): UserScheduleSettings => {
5+
const userScheduleSettingsTransformer = (
6+
settings: Prisma.Schedule_SettingsGetPayload<UserScheduleSettingsQueryArgs>
7+
): UserScheduleSettings => {
58
return {
69
drScheduleSettingsId: settings.drScheduleSettingsId,
710
personalGmail: settings.personalGmail,
811
personalZoomLink: settings.personalZoomLink,
9-
availability: settings.availability
12+
availabilities: settings.availabilities
1013
};
1114
};
1215

src/backend/src/utils/users.utils.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { User, User_Settings } from '@prisma/client';
1+
import { Prisma, User, User_Settings } from '@prisma/client';
22
import prisma from '../prisma/prisma';
33
import { HttpException, NotFoundException } from './errors.utils';
4-
import { PermissionCheck, Role, RoleEnum } from 'shared';
4+
import { isWithinSameWeek, PermissionCheck, Role, RoleEnum } from 'shared';
55
import { UserWithId } from './teams.utils';
6+
import { UserScheduleSettingsQueryArgs } from '../prisma-query-args/user.query-args';
67

78
type UserWithSettings = {
89
userSettings: User_Settings | null;
@@ -107,3 +108,41 @@ export const userHasPermission = async (
107108
export const areUsersinList = (users: User[], userList: User[]): boolean => {
108109
return users.every((user) => userList.some((u) => u.userId === user.userId));
109110
};
111+
112+
export const updateUserAvailability = async (
113+
availability: number[],
114+
userSettings: Prisma.Schedule_SettingsGetPayload<UserScheduleSettingsQueryArgs>,
115+
submitter: User,
116+
dateToCheckFor: Date
117+
) => {
118+
availability.forEach((time) => {
119+
if (time < 0 || time > 83) {
120+
throw new HttpException(400, 'Availability times have to be in range 0-83');
121+
}
122+
});
123+
const availabilityInSameWeek = userSettings.availabilities.filter((availability) =>
124+
isWithinSameWeek(availability.dateSet, dateToCheckFor)
125+
);
126+
127+
if (availabilityInSameWeek.length > 0) {
128+
await prisma.availability.update({
129+
where: { availabilityId: availabilityInSameWeek[0].availabilityId },
130+
data: {
131+
availability,
132+
dateSet: dateToCheckFor
133+
}
134+
});
135+
} else {
136+
await prisma.availability.create({
137+
data: {
138+
availability,
139+
dateSet: dateToCheckFor,
140+
scheduleSettings: {
141+
connect: {
142+
userId: submitter.userId
143+
}
144+
}
145+
}
146+
});
147+
}
148+
};

src/backend/tests/test-data/users.test-data.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ export const batmanScheduleSettings: Schedule_Settings = {
155155
drScheduleSettingsId: 'bmschedule',
156156
personalGmail: 'brucewayne@gmail.com',
157157
personalZoomLink: 'https://zoom.us/j/gotham',
158-
availability: [],
159158
userId: '69'
160159
};
161160

@@ -170,22 +169,20 @@ export const batmanUserScheduleSettings: UserScheduleSettings = {
170169
drScheduleSettingsId: 'bmschedule',
171170
personalGmail: 'brucewayne@gmail.com',
172171
personalZoomLink: 'https://zoom.us/j/gotham',
173-
availability: []
172+
availabilities: []
174173
};
175174

176175
export const wonderwomanScheduleSettings: Schedule_Settings = {
177176
drScheduleSettingsId: 'wwschedule',
178177
personalGmail: 'diana@gmail.com',
179178
personalZoomLink: 'https://zoom.us/jk/athens',
180-
availability: [3],
181179
userId: '72'
182180
};
183181

184182
export const wonderwomanMarkedScheduleSettings: Schedule_Settings = {
185183
drScheduleSettingsId: 'wwschedule',
186184
personalGmail: 'diana@gmail.com',
187185
personalZoomLink: 'https://zoom.us/jk/athens',
188-
availability: [1, 2],
189186
userId: '72'
190187
};
191188

src/frontend/src/apis/users.api.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
*/
55

66
import axios from '../utils/axios';
7-
import { Project, User, UserScheduleSettings, UserSecureSettings, UserWithScheduleSettings } from 'shared';
7+
import {
8+
Project,
9+
SetUserScheduleSettingsPayload,
10+
User,
11+
UserScheduleSettings,
12+
UserSecureSettings,
13+
UserWithScheduleSettings
14+
} from 'shared';
815
import { apiUrls } from '../utils/urls';
916
import { authUserTransformer, userTransformer } from './transformers/users.transformers';
1017
import { AuthenticatedUser, UserSettings } from 'shared';
@@ -120,7 +127,7 @@ export const updateUserSecureSettings = (settings: UserSecureSettings) => {
120127
/**
121128
* Update the given user's schedule settings by UserId
122129
*/
123-
export const updateUserScheduleSettings = (settings: UserScheduleSettings) => {
130+
export const updateUserScheduleSettings = (settings: SetUserScheduleSettingsPayload) => {
124131
return axios.post<UserScheduleSettings>(apiUrls.userScheduleSettingsSet(), settings);
125132
};
126133

0 commit comments

Comments
 (0)