Skip to content

Commit 4402495

Browse files
committed
changes to the job modal
2 parents d108d1b + 495e90a commit 4402495

23 files changed

+1305
-90
lines changed

.github/workflows/runuvicorn.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jobs:
1414
username: ${{secrets.USERNAME}}
1515
key: ${{secrets.EC2_SSH_KEY}}
1616
script: |
17+
pkill gunicorn
18+
pkill uvicorn
1719
cd ${{secrets.TARGET_DIR}}/backend
1820
python3 -m pip install -r reqs.txt
1921
cd ${{secrets.TARGET_DIR}}

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v2.3.0
3+
rev: v4.4.0
44
hooks:
55
- id: end-of-file-fixer
66
- id: trailing-whitespace
77
- repo: https://github.com/pre-commit/mirrors-prettier
8-
rev: v2.7.1
8+
rev: v3.0.0-alpha.9-for-vscode
99
hooks:
1010
- id: prettier
1111
files: '.*frontend.*\.(jsx|css)'

backend/crud/user_profile_job_crud.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sqlalchemy.orm import Session
44

55
from ..models import application_status_model, user_profile_job_model
6+
from ..crud import application_status_crud
67

78
def get_applications_by_user_id(db: Session,
89
user_id: int) -> list[user_profile_job_model.UserProfileJob]:
@@ -146,15 +147,18 @@ def create_applicant_application(db: Session, user_id: int, job_id: int) \
146147
user_profile_job_model.UserProfileJob
147148
a sqlalchemy UserProfileJob object representing the new application
148149
"""
150+
application_status_id = application_status_crud\
151+
.get_application_status_by_name(db, application_status_model.ApplicationStatusEnum.submitted).id
149152

150153
new_application = user_profile_job_model.UserProfileJob(
151154
user_profile_id=user_id,
152155
job_id=job_id,
153-
application_status_id=application_status_model.ApplicationStatusEnum.submitted.value,
156+
application_status_id=application_status_id,
154157
application_submitted_date=datetime.today(),
155158
application_reviewed_date=None,
156159
application_offer_sent_date=None,
157-
application_rejected_date=None
160+
application_rejected_date=None,
161+
rejection_feedback=None
158162
)
159163

160164
db.add(new_application)
@@ -190,3 +194,35 @@ def delete_applicant_application(db: Session, user_id: int, job_id: int) \
190194
db.delete(application)
191195
db.commit()
192196
return application
197+
198+
def update_applicant_application_status(db: Session,
199+
user_id: int,
200+
job_id: int,
201+
new_status: application_status_model.ApplicationStatusEnum,
202+
rejection_feedback: str | None) \
203+
-> user_profile_job_model.UserProfileJob:
204+
# Get the application
205+
current_application = get_application_by_user_id_and_job_id(db, user_id, job_id)
206+
207+
# Get the id for the new application status
208+
new_application_status_id = application_status_crud.get_application_status_by_name(db, new_status.value).id
209+
210+
# Update the current applications id
211+
current_application.application_status_id = new_application_status_id
212+
213+
# Update the other attributes of the current application depending on the new status
214+
if new_status.value == application_status_model.ApplicationStatusEnum.submitted.value:
215+
current_application.application_submitted_date = datetime.today()
216+
elif new_status.value == application_status_model.ApplicationStatusEnum.in_review.value:
217+
current_application.application_reviewed_date = datetime.today()
218+
elif new_status.value == application_status_model.ApplicationStatusEnum.screening.value:
219+
current_application.application_further_screening_date = datetime.today()
220+
elif new_status.value == application_status_model.ApplicationStatusEnum.rejected.value:
221+
current_application.application_rejected_date = datetime.today()
222+
current_application.rejection_feedback = rejection_feedback
223+
elif new_status.value == application_status_model.ApplicationStatusEnum.offer_sent.value:
224+
current_application.application_offer_sent_date = datetime.today()
225+
226+
db.commit()
227+
db.refresh(current_application)
228+
return current_application

backend/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
origins = [
1010
"http://localhost:8000",
1111
"https://chapi.techstartucalgary.com/docs",
12-
"http://localhost:3000"
12+
"http://localhost:3000",
13+
"https://cyberhire.techstartucalgary.com",
1314
]
1415

1516
app.add_middleware(

backend/models/user_profile_job_model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from sqlalchemy import Integer, Column, ForeignKey, Date
1+
from sqlalchemy import Integer, Column, ForeignKey, Date, String
22
from ..database import Base
33
from sqlalchemy.orm import relationship
44
from .application_status_model import ApplicationStatus
@@ -15,8 +15,10 @@ class UserProfileJob(Base):
1515
application_status_id = Column(Integer, ForeignKey("application_status.id"), nullable=False)
1616
application_submitted_date = Column(Date, nullable=False)
1717
application_reviewed_date = Column(Date, nullable=True)
18+
application_further_screening_date = Column(Date, nullable=True)
1819
application_offer_sent_date = Column(Date, nullable=True)
19-
application_rejected_date =Column(Date, nullable=True)
20+
application_rejected_date = Column(Date, nullable=True)
21+
rejection_feedback = Column(String, nullable=True)
2022

2123
application_status = relationship("ApplicationStatus")
2224
applicant = relationship("UserProfile", back_populates="applications")

backend/routers/user_profile_job_router.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends, status, HTTPException, Path
1+
from fastapi import APIRouter, Depends, status, HTTPException, Path, Body
22

33
from sqlalchemy.orm import Session
44

@@ -277,13 +277,62 @@ def delete_applicant_application(db: Session = Depends(dependencies.get_db),
277277
return f"Deleted application for job {job_id} for applicant {applicant.id}."
278278

279279

280-
# PATCH /applications/{job_id}_{applicant_id}/review change status to under review
281-
# @router.patch(
282-
# "/applications/review/"
283-
# )
280+
# PATCH /applications/{job_id}_{applicant_id}/ change status
281+
@router.patch(
282+
"/applications/{job_id}_{applicant_id}/",
283+
response_model=user_profile_job_schema.UserProfileJob,
284+
status_code=status.HTTP_200_OK,
285+
tags=["Application"],
286+
summary="PATCH route for a recruiter to change the status of an application.",
287+
description="Change the status of an application for a job and provide optional rejection feedback " \
288+
"for the applicant. The status of an application cannot move backwards. The recruiter must " \
289+
"own the job to be able to change the status of an application.",
290+
response_description="The updated application."
291+
)
292+
def change_application_status(db: Session = Depends(dependencies.get_db),
293+
job_id: int = Path(),
294+
applicant_id: int = Path(),
295+
new_status: application_status_model.ApplicationStatusEnum = Body(),
296+
rejection_feedback: str | None = Body(default=None),
297+
recruiter: user_model.User = Depends(dependencies.get_current_recruiter_user)):
298+
# Check the application exists
299+
application = user_profile_job_crud.get_application_by_user_id_and_job_id(db, applicant_id, job_id)
300+
if application is None:
301+
raise HTTPException(
302+
status_code=status.HTTP_404_NOT_FOUND,
303+
detail=f"Application for job {job_id} and applicant {applicant_id} does not exist."
304+
)
305+
306+
# Check the recruiter owns the job
307+
job = job_crud.get_job_by_id(db, job_id)
308+
309+
if job.user_profile_id != recruiter.id:
310+
raise HTTPException(
311+
status_code=status.HTTP_401_UNAUTHORIZED,
312+
detail=f"Recruiter {recruiter.id} is not authorized to update applications for job " \
313+
"{job_id}."
314+
)
315+
316+
# Check the current status of the application and the new status of the application to see if it is allowed
317+
# The new status of an application must be greater or equal to the current status of an application
318+
# except in the case of REJECTED, which is a final status it can only be equal
319+
can_change_app_status = False
320+
new_application_status_id = application_status_crud.get_application_status_by_name(db, new_status.value).id
321+
322+
if application.application_status.status == application_status_model.ApplicationStatusEnum.rejected.value:
323+
if new_application_status_id == application.application_status_id:
324+
can_change_app_status = True
325+
else:
326+
if new_application_status_id >= application. application_status_id:
327+
can_change_app_status = True
284328

285-
# PATCH /applications/{job_id}_{applicant_id}/further_screening
329+
if not can_change_app_status:
330+
raise HTTPException(
331+
status_code=status.HTTP_400_BAD_REQUEST,
332+
detail=f"Cannot change application status from {application.application_status.status} to "\
333+
f"{new_status.value}."
334+
)
286335

287-
# PATCH /applications/{job_id}_{applicant_id}/offer change status to offer sent
336+
# Update the application status of the application
337+
return user_profile_job_crud.update_applicant_application_status(db, applicant_id, job_id, new_status, rejection_feedback)
288338

289-
# PATCH /applications/{job_id}_{applicant_id}/rejected change status to rejected

backend/routers/user_profile_router.py

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,106 @@ def get_user_profile_me(db: Session = Depends(dependencies.get_db),
6767
# Ignore the type error from pylance
6868
return user_profile_crud.get_user_profile_by_id(db, current_user.id)
6969

70+
@router.get(
71+
"/users/profile/me/profile_picture",
72+
status_code=status.HTTP_200_OK,
73+
tags=["UserProfile"],
74+
summary="Retrieve the current user's profile picture from the database.",
75+
description="Retrieve the current user's profile picture from the database as a jpg file.",
76+
response_description="The current user's profile picture."
77+
)
78+
def get_user_profile_picture_me(
79+
db: Session = Depends(dependencies.get_db),
80+
current_user: user_model.User = Depends(dependencies.get_current_user)
81+
):
82+
"""
83+
A GET route to obtain the current authenticated user's profile picture.
84+
85+
Parameters
86+
----------
87+
db: Session
88+
a database session
89+
current_user: models.user_model.User
90+
a sqlalchemy pbject representing the current authenticated user
91+
92+
Returns
93+
-------
94+
fastapi.Response
95+
a response with the profile picture
96+
"""
97+
dependencies.user_profile_exists(db, current_user.id)
98+
database_user_profile : user_profile_model.UserProfile = user_profile_crud.get_user_profile_by_id(
99+
db,
100+
current_user.id
101+
)
102+
103+
if database_user_profile is not None:
104+
file = database_user_profile.profile_picture
105+
106+
if file is None:
107+
raise HTTPException(
108+
status_code=status.HTTP_404_NOT_FOUND,
109+
detail=f"User {current_user.id} does not have a profile picture in the database."
110+
)
111+
112+
return Response(
113+
content=file,
114+
media_type="image/jpg",
115+
headers={
116+
"content-disposition": f"attachment; \
117+
filename={current_user.username}_profile_picture.jpg"
118+
}
119+
)
120+
121+
@router.get(
122+
"/users/profile/me/resume",
123+
status_code=status.HTTP_200_OK,
124+
tags=["UserProfile"],
125+
summary="Retrieve the current user's resume from the database.",
126+
description="Retrieve the current user's resume from the database as a pdf file.",
127+
response_description="The current user's resume."
128+
)
129+
def get_user_resume_me(
130+
db: Session = Depends(dependencies.get_db),
131+
current_user: user_model.User = Depends(dependencies.get_current_user)
132+
):
133+
"""
134+
A GET route to obtain the current authenticated user's resume.
135+
136+
Parameters
137+
----------
138+
db: Session
139+
a database session
140+
current_user: models.user_model.User
141+
a sqlalchemy pbject representing the current authenticated user
142+
143+
Returns
144+
-------
145+
fastapi.Response
146+
a response with the resume
147+
"""
148+
dependencies.user_profile_exists(db, current_user.id)
149+
database_user_profile : user_profile_model.UserProfile = user_profile_crud.get_user_profile_by_id(
150+
db,
151+
current_user.id
152+
)
153+
154+
if database_user_profile is not None:
155+
file = database_user_profile.resume
156+
157+
if file is None:
158+
raise HTTPException(
159+
status_code=status.HTTP_404_NOT_FOUND,
160+
detail=f"User {current_user.id} does not have a resume in the database."
161+
)
162+
163+
return Response(content=file, media_type="application/pdf",
164+
headers={
165+
"content-disposition": f"attachment; \
166+
filename={current_user.username}_resume.pdf"
167+
}
168+
)
169+
70170
@router.get(
71171
"/users/profile/{username}",
72172
response_model=user_profile_schema.UserProfile | None,
@@ -114,7 +214,7 @@ def create_user_profile(*, db: Session = Depends(dependencies.get_db),
114214
):
115215
"""
116216
A POST route to create a new user profile for the current authenticated user.
117-
217+
118218
Parameters
119219
----------
120220
db: Session
@@ -123,7 +223,7 @@ def create_user_profile(*, db: Session = Depends(dependencies.get_db),
123223
a sqlalchemy User object representing the current authenticated user
124224
user_profile: schemas.user_profile_schema.UserProfileCreate
125225
a pydantic model representing the current authenticated user's profile information
126-
226+
127227
Returns
128228
-------
129229
schemas.user_profile_schema.UserProfile
@@ -158,7 +258,7 @@ def delete_user_profile_me(db: Session = Depends(dependencies.get_db),
158258
a database session
159259
current_user:models.user_model.User
160260
a sqlalchemy User object representing the current authenticated user
161-
261+
162262
Returns
163263
-------
164264
schemas.user_profile_schema.UserProfile

backend/schemas/user_profile_job_schema.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ class UserProfileJob(UserProfileJobBase):
4444
application_status_id : int
4545
application_submitted_date : date | None
4646
application_reviewed_date : date | None
47+
application_further_screening_date : date | None
4748
application_offer_sent_date : date | None
4849
application_rejected_date : date | None
50+
rejection_feedback : str | None
4951
application_status : ApplicationStatus
5052
applicant : UserProfile
5153
job: Job
@@ -58,12 +60,13 @@ class Config:
5860
"job_id": 1,
5961
"application_status_id": 1,
6062
"application_submitted_date": "2023-01-01",
61-
"application_reviewed_date": None,
62-
"application_offer_sent_date": None,
63+
"application_reviewed_date": "2023-01-02",
64+
"application_further_screening_date": "2023-01-02",
65+
"application_offer_sent_date": "2023-01-03",
6366
"application_rejected_date": None,
6467
"application_status": {
6568
"id": 1,
66-
"status": "Application Submitted"
69+
"status": "SUBMITTED"
6770
},
6871
"applicant": {
6972
"user_id": 1,

backend/sql/cyberhire_ddl.sql

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ CREATE TABLE IF NOT EXISTS cyberhire."application_status" (
8484

8585
INSERT INTO cyberhire."application_status" (status)
8686
VALUES
87-
('Application Submitted'),
88-
('Application Under Review'),
89-
('Offer Sent'),
90-
('Rejected')
87+
('SUBMITTED'),
88+
('UNDER REVIEW'),
89+
('UNDERGOING FURTHER SCREENING')
90+
('REJECTED'),
91+
('OFFER SENT')
9192
;
9293

9394
-- Create for the user_profile_job table
@@ -97,10 +98,12 @@ CREATE TABLE IF NOT EXISTS cyberhire."user_profile_job" (
9798
application_status_id int4 NOT NULL,
9899
application_submitted_date date NOT NULL,
99100
application_reviewed_date date NULL,
101+
application_further_screening_date date NULL,
100102
application_offer_sent_date date NULL,
101103
application_rejected_date date NULL,
104+
rejection_feedback varchar(2000) NULL,
102105
CONSTRAINT user_profile_job_pkey PRIMARY KEY (user_profile_id, job_id),
103106
CONSTRAINT user_profile_job_fk FOREIGN KEY (user_profile_id) REFERENCES cyberhire."user_profile" (user_id),
104107
CONSTRAINT user_profile_job_fk2 FOREIGN KEY (job_id) REFERENCES cyberhire."job" (id),
105108
CONSTRAINT user_profile_job_fk3 FOREIGN KEY (application_status_id) REFERENCES application_status (id)
106-
);
109+
);

0 commit comments

Comments
 (0)