Skip to content

Commit 55e8bc7

Browse files
authored
Add volunteer type for NFC/event scanning (VandyHacks#479)
* Add volunteer type * Add new func to check authorized against array of usertypes * Only let organizers register user with NFC id * Add volunteer schema types * Make volunteers resolve * Fix something that's not an issue
1 parent 79a2541 commit 55e8bc7

File tree

5 files changed

+99
-47
lines changed

5 files changed

+99
-47
lines changed

codegen.json

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
"watch": false,
66
"generates": {
77
"src/server/generated/graphql.ts": {
8-
"plugins": [
9-
"typescript",
10-
"typescript-resolvers",
11-
"typescript-mongodb"
12-
],
8+
"plugins": ["typescript", "typescript-resolvers", "typescript-mongodb"],
139
"config": {
1410
"mappers": {
1511
"ApplicationField": "ApplicationFieldDbObject",
@@ -22,10 +18,11 @@
2218
"Mentor": "MentorDbObject",
2319
"Organizer": "OrganizerDbObject",
2420
"Shift": "ShiftDbObject",
25-
"Sponsor": "SponsorDbObject",
26-
"Team": "TeamDbObject",
27-
"Tier": "TierDbObject",
28-
"User": "UserDbInterface"
21+
"Sponsor": "SponsorDbObject",
22+
"Team": "TeamDbObject",
23+
"Tier": "TierDbObject",
24+
"User": "UserDbInterface",
25+
"Volunteer": "VolunteerDbObject"
2926
},
3027
"skipTypenames": false
3128
}

src/client/assets/routes.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const routes = [
2020
path: '/dashboard',
2121
},
2222
{
23-
authLevel: [UserType.Hacker],
23+
authLevel: [UserType.Hacker, UserType.Volunteer],
2424
component: HackerDash,
2525
displayText: 'Dashboard',
2626
path: '/dashboard',
@@ -38,7 +38,7 @@ const routes = [
3838
path: '/dashboard',
3939
},
4040
{
41-
authLevel: [UserType.Hacker],
41+
authLevel: [UserType.Hacker, UserType.Volunteer, UserType.Organizer],
4242
component: Application,
4343
displayText: 'Apply',
4444
path: '/application',
@@ -74,13 +74,13 @@ const routes = [
7474
path: '/manage/sponsors',
7575
},
7676
{
77-
authLevel: [UserType.Hacker, UserType.Sponsor, UserType.Organizer],
77+
authLevel: [UserType.Hacker, UserType.Sponsor, UserType.Volunteer],
7878
component: Help,
7979
displayText: 'Help',
8080
path: '/help',
8181
},
8282
{
83-
authLevel: [UserType.Organizer, UserType.Sponsor],
83+
authLevel: [UserType.Organizer, UserType.Volunteer, UserType.Sponsor],
8484
component: Nfc,
8585
displayText: 'Scan NFC',
8686
path: '/nfc',

src/common/schema.graphql.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ export default gql`
1919
eventsAttended: [ID!]! @column
2020
}
2121
22-
enum AuthLevel {
23-
HACKER
24-
ORGANIZER
25-
SPONSOR
26-
}
27-
2822
enum DietaryRestriction {
2923
VEGETARIAN
3024
VEGAN
@@ -87,6 +81,7 @@ export default gql`
8781
ORGANIZER
8882
SPONSOR
8983
SUPER_ADMIN
84+
VOLUNTEER
9085
}
9186
9287
enum SortDirection {
@@ -248,6 +243,35 @@ export default gql`
248243
eventsAttended: [ID!]! @column
249244
}
250245
246+
type Volunteer implements User @entity {
247+
id: ID! @id
248+
createdAt: Float!
249+
secondaryIds: [ID!]!
250+
logins: [Login!]!
251+
email: String!
252+
firstName: String!
253+
preferredName: String!
254+
lastName: String!
255+
shirtSize: ShirtSize
256+
gender: String
257+
dietaryRestrictions: String!
258+
userType: UserType!
259+
phoneNumber: String
260+
race: String! @column
261+
modifiedAt: Float! @column
262+
status: ApplicationStatus! @column
263+
school: String @column
264+
gradYear: String @column
265+
majors: [String!]! @column
266+
adult: Boolean @column
267+
volunteer: String @column
268+
github: String @column
269+
team: Team @embedded
270+
eventsAttended: [ID!]! @column
271+
application: [ApplicationField!]! @embedded
272+
emailUnsubscribed: Boolean! @column
273+
}
274+
251275
type Query {
252276
company(id: ID!): Company!
253277
companies(sortDirection: SortDirection): [Company!]!

src/server/resolvers/helpers.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export async function fetchUser(
124124
{ email, userType }: { email: string; userType: string },
125125
models: Models
126126
): Promise<UserDbInterface> {
127-
if (userType === UserType.Hacker) {
127+
if (userType === UserType.Hacker || userType === UserType.Volunteer) {
128128
return query({ email }, models.Hackers);
129129
}
130130
if (userType === UserType.Organizer) {
@@ -152,6 +152,26 @@ export function checkIsAuthorized<T extends UserDbInterface>(requiredType: UserT
152152

153153
return user;
154154
}
155+
156+
/**
157+
* Funtion to check if the user has the authorization required to continue (checks an ARRAY of types))
158+
* If not, the function will throw a GraphQL AuthenticationError.
159+
* @param requiredType The authorization level the user should have.
160+
* @param user The user to check against requiredType.
161+
* @returns The user object, coerced to a non-null type.
162+
*/
163+
export function checkIsAuthorizedArray<T extends UserDbInterface>(
164+
requiredTypes: UserType[],
165+
user?: T
166+
): T {
167+
if (!user || !requiredTypes.includes(user.userType as UserType)) {
168+
throw new AuthenticationError(
169+
`user ${user && user.email}: ${JSON.stringify(user)} must be one of "${requiredTypes}"`
170+
);
171+
}
172+
173+
return user;
174+
}
155175
/**
156176
* Replaces the resume field with a signed URL
157177
* @param appFields Fields to search for a resume field to replace

src/server/resolvers/index.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
updateUser,
2121
checkIsAuthorized,
2222
replaceResumeFieldWithLink,
23+
checkIsAuthorizedArray,
2324
} from './helpers';
2425
import { checkInUserToEvent, removeUserFromEvent, registerNFCUIDWithUser, getUser } from '../nfc';
2526
import { addOrUpdateEvent } from '../events';
@@ -51,7 +52,9 @@ const requiredFields = [
5152
*/
5253
export type CustomResolvers<T> = Omit<Resolvers<T>, 'User'> & {
5354
User: {
54-
__resolveType: (user: UserDbInterface) => 'Hacker' | 'Mentor' | 'Organizer' | 'Sponsor';
55+
__resolveType: (
56+
user: UserDbInterface
57+
) => 'Hacker' | 'Mentor' | 'Organizer' | 'Sponsor' | 'Volunteer';
5558
};
5659
};
5760

@@ -76,6 +79,30 @@ const userResolvers: Omit<UserResolvers, '__resolveType' | 'userType'> = {
7679
},
7780
};
7881

82+
const hackerResolvers: CustomResolvers<Context>['Hacker'] = {
83+
...userResolvers,
84+
adult: async hacker => (await hacker).adult || null,
85+
application: async (hacker, args, { models }: Context) =>
86+
replaceResumeFieldWithLink(
87+
models.ApplicationFields.find({ userId: (await hacker)._id }).toArray()
88+
),
89+
gender: async hacker => (await hacker).gender || null,
90+
github: async hacker => (await hacker).github || null,
91+
gradYear: async hacker => (await hacker).gradYear || null,
92+
majors: async hacker => (await hacker).majors || [],
93+
modifiedAt: async hacker => (await hacker).modifiedAt,
94+
race: async hacker => (await hacker).race || '',
95+
school: async hacker => (await hacker).school || null,
96+
status: async hacker => toEnum(ApplicationStatus)((await hacker).status),
97+
team: async (hacker, args, { models }) => {
98+
const team = await models.UserTeamIndicies.findOne({ email: (await hacker).email });
99+
if (!team) return { _id: new ObjectID(), createdAt: new Date(0), memberIds: [], name: '' };
100+
return query({ name: team.team }, models.Teams);
101+
},
102+
userType: () => UserType.Hacker,
103+
volunteer: async hacker => (await hacker).volunteer || null,
104+
};
105+
79106
export const resolvers: CustomResolvers<Context> = {
80107
/**
81108
* These resolvers are for querying fields
@@ -114,28 +141,10 @@ export const resolvers: CustomResolvers<Context> = {
114141
name: async tier => (await tier).name,
115142
permissions: async tier => (await tier).permissions,
116143
},
117-
Hacker: {
118-
...userResolvers,
119-
adult: async hacker => (await hacker).adult || null,
120-
application: async (hacker, args, { models }: Context) =>
121-
replaceResumeFieldWithLink(
122-
models.ApplicationFields.find({ userId: (await hacker)._id }).toArray()
123-
),
124-
gender: async hacker => (await hacker).gender || null,
125-
github: async hacker => (await hacker).github || null,
126-
gradYear: async hacker => (await hacker).gradYear || null,
127-
majors: async hacker => (await hacker).majors || [],
128-
modifiedAt: async hacker => (await hacker).modifiedAt,
129-
race: async hacker => (await hacker).race || '',
130-
school: async hacker => (await hacker).school || null,
131-
status: async hacker => toEnum(ApplicationStatus)((await hacker).status),
132-
team: async (hacker, args, { models }) => {
133-
const team = await models.UserTeamIndicies.findOne({ email: (await hacker).email });
134-
if (!team) return { _id: new ObjectID(), createdAt: new Date(0), memberIds: [], name: '' };
135-
return query({ name: team.team }, models.Teams);
136-
},
137-
userType: () => UserType.Hacker,
138-
volunteer: async hacker => (await hacker).volunteer || null,
144+
Hacker: hackerResolvers,
145+
Volunteer: {
146+
...hackerResolvers,
147+
userType: () => UserType.Volunteer,
139148
},
140149
Login: {
141150
createdAt: async login => (await login).createdAt.getTime(),
@@ -164,7 +173,7 @@ export const resolvers: CustomResolvers<Context> = {
164173
return addOrUpdateEvent(input, models);
165174
},
166175
checkInUserToEvent: async (root, { input }, { models, user }) => {
167-
checkIsAuthorized(UserType.Organizer, user);
176+
checkIsAuthorizedArray([UserType.Organizer, UserType.Volunteer, UserType.Sponsor], user);
168177
const userRet = await checkInUserToEvent(input.user, input.event, models);
169178
return userRet;
170179
},
@@ -265,7 +274,7 @@ export const resolvers: CustomResolvers<Context> = {
265274
return companyCreated;
266275
},
267276
checkInUserToEventByNfc: async (root, { input }, { models, user }) => {
268-
checkIsAuthorized(UserType.Organizer, user);
277+
checkIsAuthorizedArray([UserType.Organizer, UserType.Volunteer, UserType.Sponsor], user);
269278
const inputUser = await getUser(input.nfcId, models);
270279
if (inputUser) {
271280
const userRet = await checkInUserToEvent(inputUser._id.toString(), input.event, models);
@@ -364,12 +373,12 @@ export const resolvers: CustomResolvers<Context> = {
364373
return userRet;
365374
},
366375
removeUserFromEvent: async (root, { input }, { models, user }) => {
367-
checkIsAuthorized(UserType.Organizer, user);
376+
checkIsAuthorizedArray([UserType.Organizer, UserType.Volunteer, UserType.Sponsor], user);
368377
const userRet = await removeUserFromEvent(input.user, input.event, models);
369378
return userRet;
370379
},
371380
removeUserFromEventByNfc: async (root, { input }, { models, user }) => {
372-
checkIsAuthorized(UserType.Organizer, user);
381+
checkIsAuthorizedArray([UserType.Organizer, UserType.Volunteer, UserType.Sponsor], user);
373382
const inputUser = await getUser(input.nfcId, models);
374383
if (inputUser) {
375384
const userRet = await removeUserFromEvent(inputUser._id.toString(), input.event, models);
@@ -561,6 +570,8 @@ export const resolvers: CustomResolvers<Context> = {
561570
return 'Organizer';
562571
case UserType.Sponsor:
563572
return 'Sponsor';
573+
case UserType.Volunteer:
574+
return 'Volunteer';
564575
default:
565576
throw new AuthenticationError(`cannot decode UserType "${user.userType}`);
566577
}

0 commit comments

Comments
 (0)