Skip to content

Commit a53410d

Browse files
authored
Merge pull request #45 from Women-in-Computing-at-RIT/feature/checkIn
Resume Downloader
2 parents 810f1ca + 02e4c7a commit a53410d

7 files changed

Lines changed: 106 additions & 18 deletions

File tree

api/src/controller/userSearch.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from flask_restful import Resource, reqparse
2+
from flask import request
3+
from data.users import getUsers
4+
from utils.authentication import authenticate
5+
from data.permissions import canAccessUserData
6+
7+
8+
class UserSearch(Resource):
9+
PATH = '/user/search'
10+
11+
def get(self):
12+
authenticationPayload = authenticate(request.headers)
13+
if authenticationPayload is None:
14+
return {"message": "Must be logged in"}, 401
15+
auth0_id = authenticationPayload['sub']
16+
17+
permissions = canAccessUserData(auth0_id)
18+
if permissions is None:
19+
return {"message": "Internal Server Error"}, 500
20+
if not (permissions or authenticationPayload['gty'] == 'client-credentials'):
21+
# check that the grant type is client-credentials which will exist only for the machine to machine
22+
# auth0 connections using client id and secret, aka: discord bots and s3 resume downloader
23+
return {"message": "Permission Denied"}, 403
24+
25+
parser = reqparse.RequestParser()
26+
parser.add_argument('firstName', type=str, required=False)
27+
parser.add_argument('lastName', type=str, required=False)
28+
parser.add_argument('email', type=str, required=False)
29+
parser.add_argument('applicationId', type=str, required=False)
30+
parser.add_argument('userId', type=str, required=False)
31+
parser.add_argument('recipientStatusFilter', type=str, required=False, action="append")
32+
args = parser.parse_args()
33+
34+
matchingUsers = getUsers(applicationStatusFilterList=args['recipientStatusFilter'],
35+
firstName=args['firstName'],
36+
lastName=args['lastName'],
37+
email=args['email'],
38+
applicationId=args['applicationId'],
39+
userId=args['userId'])
40+
41+
if matchingUsers is None:
42+
return {"Message": "Internal Server Error"}, 500
43+
return matchingUsers

api/src/controller/users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from utils.authentication import authenticate
55
from data.permissions import canAccessUserData
66

7-
87
class Users(Resource):
98
PATH = '/users'
109

@@ -13,6 +12,7 @@ def get(self):
1312
if authenticationPayload is None:
1413
return {"message": "Must be logged in"}, 401
1514
auth0_id = authenticationPayload['sub']
15+
1616
permissions = canAccessUserData(auth0_id)
1717
if permissions is None:
1818
return {"message": "Internal Server Error"}, 500

api/src/data/users.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,61 @@ def getUserQuery():
2020
"LEFT JOIN Sponsors as s ON u.sponsor_id = s.sponsor_id "
2121

2222

23-
def getUsers(status=None, is_virtual=None) -> list:
23+
def getUsers(applicationStatusFilterList: List[str] = None, is_virtual=None, firstName=None, lastName=None, userId=None,
24+
email=None, applicationId=None) -> list:
2425
"""
2526
Get a filtered list of all users in json/dictionary format
26-
:param status: string matching user application status
27+
:param applicationId:
28+
:param email:
29+
:param userId:
30+
:param lastName:
31+
:param firstName:
32+
:param applicationStatusFilterList: list of statuses to match
2733
:param is_virtual: boolean for if you want all virtual/in person users
28-
:return: list of dictionaries with user information
34+
:return: list of dictionaries with user information, None if error
2935
"""
30-
3136
sql = getUserQuery()
37+
firstWhereClause = True
3238
args = ()
33-
if status is not None:
34-
sql += "WHERE app.status = %s"
35-
args = args + (status,)
36-
if is_virtual is not None:
37-
sql += "AND WHERE app.is_virtual = %s"
38-
args = args + (is_virtual,)
39-
elif is_virtual is not None:
40-
sql += "WHERE app.is_virtual = %s"
39+
40+
if applicationStatusFilterList is not None:
41+
sql += f" WHERE app.status in ({','.join(['%s'] * len(applicationStatusFilterList))}) "
42+
for status in applicationStatusFilterList:
43+
args = args + (status,)
44+
if is_virtual is not None:
45+
sql += getOptionalAnd(firstWhereClause) + " WHERE app.is_virtual = %s "
4146
args = args + (is_virtual,)
47+
if firstName is not None:
48+
sql += getOptionalAnd(firstWhereClause) + " WHERE u.first_name = %s "
49+
args = args + (firstName,)
50+
if lastName is not None:
51+
sql += getOptionalAnd(firstWhereClause) + " WHERE u.last_name = %s "
52+
args = args + (lastName,)
53+
if userId is not None:
54+
sql += getOptionalAnd(firstWhereClause) + " WHERE u.user_id = %s "
55+
args = args + (userId,)
56+
if email is not None:
57+
sql += getOptionalAnd(firstWhereClause) + " WHERE u.email = %s "
58+
args = args + (email,)
59+
if applicationId is not None:
60+
sql += getOptionalAnd(firstWhereClause) + " WHERE app.application_id = %s "
61+
args = args + (applicationId,)
4262

4363
return exec_get_all(sql, args)
4464

4565

66+
def getOptionalAnd(isFirstWhereClause) -> str:
67+
"""
68+
Helper function, returns "AND " if clause isn't the first and empty string if it is
69+
:param isFirstWhereClause:
70+
:return:
71+
"""
72+
if isFirstWhereClause:
73+
return ""
74+
else:
75+
return " AND "
76+
77+
4678
def getUserByAuthID(auth_id) -> dict:
4779
"""
4880
wrapper for getting user using auth0 id
@@ -94,7 +126,7 @@ def getUserById(auth_id=None, user_id=None) -> dict:
94126
if auth_id is not None:
95127
sql += "WHERE u.auth0_id = %s"
96128
args = args + (auth_id,)
97-
if user_id is not None:
129+
elif user_id is not None:
98130
sql += "WHERE u.user_id = %s"
99131
args = args + (user_id,)
100132

@@ -121,4 +153,3 @@ def getUserEmailsWithFilter(applicationStatusFilterList: List[str]):
121153
if len(u['email']) > 1:
122154
emailList.append(u['email'])
123155
return emailList
124-

api/src/db/db_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def exec_get_one(sql, args={}) -> (dict, bool):
109109
return None, True
110110

111111

112-
def exec_get_all(sql, args={}) -> dict:
112+
def exec_get_all(sql, args={}) -> list:
113113
"""
114114
Retrieves many records from the dictionary
115115
:param sql:
@@ -118,6 +118,7 @@ def exec_get_all(sql, args={}) -> dict:
118118
"""
119119
conn = connect()
120120
if conn is None:
121+
logger.error("SQL Connection Error")
121122
return None
122123
cur = conn.cursor(dictionary=True)
123124
try:

api/src/db/migration/migration.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import db.db_utils as db_utils
44
import glob
55
import logging
6+
67
logger = logging.getLogger("Migrations")
78

89
version = None
@@ -17,18 +18,24 @@ def initializeMigrations() -> bool:
1718
global version
1819
global dirtyVersion
1920
migrationInformation = getCurrentMigration()
21+
if migrationInformation is None:
22+
# db error
23+
return False
2024
version = migrationInformation["version"]
2125
dirtyVersion = migrationInformation["dirtyVersion"]
2226
return not dirtyVersion
2327

28+
2429
def getCurrentMigration() -> dict:
2530
sql = "SELECT version, dirtyVersion FROM Migrations ORDER BY version DESC LIMIT 1;"
2631
migrationInformation, didError = db_utils.exec_get_one(sql)
2732
if migrationInformation is None or didError:
28-
logger.error("Migration Information is None")
29-
return None, True
33+
logger.error("Migration Information is None migrationInformation: %s didError: %s", migrationInformation,
34+
didError)
35+
return None
3036
return migrationInformation
3137

38+
3239
def up() -> bool:
3340
global version
3441
global dirtyVersion
@@ -38,6 +45,8 @@ def up() -> bool:
3845

3946
# get current state of migrations
4047
migrationInformation = getCurrentMigration()
48+
if migrationInformation is None:
49+
return False
4150
version = migrationInformation["version"]
4251
dirtyVersion = migrationInformation["dirtyVersion"]
4352

@@ -85,6 +94,8 @@ def down(rollbackNumber=1) -> bool:
8594

8695
# get current state of migrations
8796
migrationInformation = getCurrentMigration()
97+
if migrationInformation is None:
98+
return False
8899
version = migrationInformation["version"]
89100
dirtyVersion = migrationInformation["dirtyVersion"]
90101

api/src/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from controller.email import Email
1818
from controller.emailPreset import EmailPreset
1919
from controller.confirmation import Confirmation
20+
from controller.userSearch import UserSearch
2021
from controller.discordIntegration import DiscordIntegration
2122
from controller.discord import Discord
2223
from db.migration import migration
@@ -53,6 +54,7 @@
5354
api.add_resource(Email, Email.PATH)
5455
api.add_resource(EmailPreset, EmailPreset.PATH)
5556
api.add_resource(Confirmation, Confirmation.PATH)
57+
api.add_resource(UserSearch, UserSearch.PATH)
5658
api.add_resource(DiscordIntegration, DiscordIntegration.PATH)
5759
api.add_resource(Discord, Discord.PATH)
5860

ui/src/pages/manage/checkin.js

Whitespace-only changes.

0 commit comments

Comments
 (0)