Spaces:
Build error
Build error
Upload 245 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +1 -0
- Dockerfile +28 -0
- Readme.md +3 -0
- __init__.py +0 -0
- __pycache__/app.cpython-310.pyc +0 -0
- __pycache__/manage.cpython-310.pyc +0 -0
- alembic.ini +86 -0
- api/__init__.py +1 -0
- api/__pycache__/__init__.cpython-310.pyc +0 -0
- api/__pycache__/api.cpython-310.pyc +0 -0
- api/api.py +48 -0
- api/endpoints/__init__.py +0 -0
- api/endpoints/__pycache__/__init__.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/assignment.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/assignment_upload.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/auth.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/class_session.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/course.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/department.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/group.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/personal_note.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/program.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/quiz.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/quiz_answer.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/school.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/teacher_note.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/two_fa.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/users.cpython-310.pyc +0 -0
- api/endpoints/__pycache__/utils.cpython-310.pyc +0 -0
- api/endpoints/assignment.py +188 -0
- api/endpoints/assignment_upload.py +235 -0
- api/endpoints/auth.py +332 -0
- api/endpoints/class_session.py +300 -0
- api/endpoints/course.py +75 -0
- api/endpoints/department.py +95 -0
- api/endpoints/group.py +130 -0
- api/endpoints/personal_note.py +195 -0
- api/endpoints/program.py +78 -0
- api/endpoints/quiz.py +456 -0
- api/endpoints/quiz_answer.py +149 -0
- api/endpoints/school.py +67 -0
- api/endpoints/teacher_note.py +61 -0
- api/endpoints/two_fa.py +151 -0
- api/endpoints/user_permission.py +49 -0
- api/endpoints/users.py +261 -0
- api/endpoints/utils.py +16 -0
- app.py +100 -0
- core/__init__.py +1 -0
- core/__pycache__/__init__.cpython-310.pyc +0 -0
- core/__pycache__/cache.cpython-310.pyc +0 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
venv
|
Dockerfile
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-alpine AS builder
|
2 |
+
|
3 |
+
WORKDIR /app/deps
|
4 |
+
|
5 |
+
COPY ./pyproject.toml .
|
6 |
+
COPY ./poetry.lock .
|
7 |
+
|
8 |
+
RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo g++ libxslt-dev postgresql-dev build-base
|
9 |
+
|
10 |
+
RUN pip install poetry
|
11 |
+
RUN poetry export --without-hashes -f requirements.txt --output requirements.txt
|
12 |
+
RUN pip wheel -r requirements.txt -w /whls
|
13 |
+
|
14 |
+
FROM python:3.9-alpine
|
15 |
+
RUN apk add libpq
|
16 |
+
|
17 |
+
WORKDIR /deps
|
18 |
+
COPY --from=builder /whls /deps
|
19 |
+
RUN pip install *.whl
|
20 |
+
RUN rm -rf *
|
21 |
+
|
22 |
+
WORKDIR /app
|
23 |
+
COPY ./ .
|
24 |
+
#RUN mv ./misc/etc/gunicorn.conf.py .
|
25 |
+
|
26 |
+
EXPOSE 7860
|
27 |
+
|
28 |
+
CMD ["python app.py"]
|
Readme.md
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Gurukul
|
2 |
+
|
3 |
+
### First steps
|
__init__.py
ADDED
File without changes
|
__pycache__/app.cpython-310.pyc
ADDED
Binary file (2.1 kB). View file
|
|
__pycache__/manage.cpython-310.pyc
ADDED
Binary file (7.85 kB). View file
|
|
alembic.ini
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# A generic, single database configuration.
|
2 |
+
|
3 |
+
[alembic]
|
4 |
+
# path to migration scripts
|
5 |
+
script_location = migrations
|
6 |
+
|
7 |
+
# template used to generate migration files
|
8 |
+
# file_template = %%(rev)s_%%(slug)s
|
9 |
+
|
10 |
+
# timezone to use when rendering the date
|
11 |
+
# within the migration file as well as the filename.
|
12 |
+
# string value is passed to dateutil.tz.gettz()
|
13 |
+
# leave blank for localtime
|
14 |
+
# timezone =
|
15 |
+
|
16 |
+
# max length of characters to apply to the
|
17 |
+
# "slug" field
|
18 |
+
# truncate_slug_length = 40
|
19 |
+
|
20 |
+
# set to 'true' to run the environment during
|
21 |
+
# the 'revision' command, regardless of autogenerate
|
22 |
+
# revision_environment = false
|
23 |
+
|
24 |
+
# set to 'true' to allow .pyc and .pyo files without
|
25 |
+
# a source .py file to be detected as revisions in the
|
26 |
+
# versions/ directory
|
27 |
+
# sourceless = false
|
28 |
+
|
29 |
+
# version location specification; this defaults
|
30 |
+
# to migrations/versions. When using multiple version
|
31 |
+
# directories, initial revisions must be specified with --version-path
|
32 |
+
# version_locations = %(here)s/bar %(here)s/bat migrations/versions
|
33 |
+
|
34 |
+
# the output encoding used when revision files
|
35 |
+
# are written from script.py.mako
|
36 |
+
# output_encoding = utf-8
|
37 |
+
|
38 |
+
sqlalchemy.url = postgresql://postadmin:postpass@localhost/siksalaya
|
39 |
+
|
40 |
+
|
41 |
+
[post_write_hooks]
|
42 |
+
# post_write_hooks defines scripts or Python functions that are run
|
43 |
+
# on newly generated revision scripts. See the documentation for further
|
44 |
+
# detail and examples
|
45 |
+
|
46 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
47 |
+
# hooks=black
|
48 |
+
# black.type=console_scripts
|
49 |
+
# black.entrypoint=black
|
50 |
+
# black.options=-l 79
|
51 |
+
|
52 |
+
# Logging configuration
|
53 |
+
[loggers]
|
54 |
+
keys = root,sqlalchemy,alembic
|
55 |
+
|
56 |
+
[handlers]
|
57 |
+
keys = console
|
58 |
+
|
59 |
+
[formatters]
|
60 |
+
keys = generic
|
61 |
+
|
62 |
+
[logger_root]
|
63 |
+
level = WARN
|
64 |
+
handlers = console
|
65 |
+
qualname =
|
66 |
+
|
67 |
+
[logger_sqlalchemy]
|
68 |
+
level = WARN
|
69 |
+
handlers =
|
70 |
+
qualname = sqlalchemy.engine
|
71 |
+
|
72 |
+
[logger_alembic]
|
73 |
+
level = INFO
|
74 |
+
handlers =
|
75 |
+
qualname = alembic
|
76 |
+
|
77 |
+
[handler_console]
|
78 |
+
class = StreamHandler
|
79 |
+
args = (sys.stderr,)
|
80 |
+
level = NOTSET
|
81 |
+
formatter = generic
|
82 |
+
|
83 |
+
[formatter_generic]
|
84 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
85 |
+
datefmt = %H:%M:%S
|
86 |
+
|
api/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .api import api_router as router
|
api/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (171 Bytes). View file
|
|
api/__pycache__/api.cpython-310.pyc
ADDED
Binary file (1.36 kB). View file
|
|
api/api.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
|
3 |
+
from api.endpoints import program, quiz_answer, teacher_note, users, group, quiz
|
4 |
+
from api.endpoints import (
|
5 |
+
program,
|
6 |
+
users,
|
7 |
+
auth,
|
8 |
+
two_fa,
|
9 |
+
utils,
|
10 |
+
course,
|
11 |
+
school,
|
12 |
+
department,
|
13 |
+
class_session,
|
14 |
+
personal_note,
|
15 |
+
teacher_note,
|
16 |
+
assignment,
|
17 |
+
assignment_upload,
|
18 |
+
)
|
19 |
+
|
20 |
+
api_router = APIRouter()
|
21 |
+
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
22 |
+
api_router.include_router(
|
23 |
+
two_fa.router, prefix="/2fa", tags=["Two Factor Authentication"]
|
24 |
+
)
|
25 |
+
api_router.include_router(users.router, prefix="/users", tags=["Users"])
|
26 |
+
api_router.include_router(utils.router, prefix="/utils", tags=["Utils"])
|
27 |
+
api_router.include_router(school.router, prefix="/school", tags=["Schools"])
|
28 |
+
api_router.include_router(course.router, prefix="/course", tags=["Courses"])
|
29 |
+
api_router.include_router(department.router, prefix="/department", tags=["Departments"])
|
30 |
+
api_router.include_router(
|
31 |
+
class_session.router, prefix="/class_session", tags=["Class Sessions"]
|
32 |
+
)
|
33 |
+
api_router.include_router(
|
34 |
+
personal_note.router, prefix="/personal_note", tags=["Personal Notes"]
|
35 |
+
)
|
36 |
+
api_router.include_router(program.router, prefix="/program", tags=["Programs"])
|
37 |
+
api_router.include_router(
|
38 |
+
teacher_note.router, prefix="/teacher_note", tags=["Teacher Notes"]
|
39 |
+
)
|
40 |
+
api_router.include_router(group.router, prefix="/group", tags=["Groups"])
|
41 |
+
api_router.include_router(quiz.router, prefix="/quiz", tags=["Quizzes"])
|
42 |
+
api_router.include_router(
|
43 |
+
quiz_answer.router, prefix="/quizanswer", tags=["Quiz Answers"]
|
44 |
+
)
|
45 |
+
api_router.include_router(assignment.router, prefix="/assignment", tags=["Assignments"])
|
46 |
+
api_router.include_router(
|
47 |
+
assignment_upload.router, prefix="/assignmentupload", tags=["Assignment Uploads"]
|
48 |
+
)
|
api/endpoints/__init__.py
ADDED
File without changes
|
api/endpoints/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (135 Bytes). View file
|
|
api/endpoints/__pycache__/assignment.cpython-310.pyc
ADDED
Binary file (4.37 kB). View file
|
|
api/endpoints/__pycache__/assignment_upload.cpython-310.pyc
ADDED
Binary file (4.85 kB). View file
|
|
api/endpoints/__pycache__/auth.cpython-310.pyc
ADDED
Binary file (8.24 kB). View file
|
|
api/endpoints/__pycache__/class_session.cpython-310.pyc
ADDED
Binary file (6.49 kB). View file
|
|
api/endpoints/__pycache__/course.cpython-310.pyc
ADDED
Binary file (2.07 kB). View file
|
|
api/endpoints/__pycache__/department.cpython-310.pyc
ADDED
Binary file (2.57 kB). View file
|
|
api/endpoints/__pycache__/group.cpython-310.pyc
ADDED
Binary file (3.12 kB). View file
|
|
api/endpoints/__pycache__/personal_note.cpython-310.pyc
ADDED
Binary file (3.45 kB). View file
|
|
api/endpoints/__pycache__/program.cpython-310.pyc
ADDED
Binary file (2.36 kB). View file
|
|
api/endpoints/__pycache__/quiz.cpython-310.pyc
ADDED
Binary file (8.89 kB). View file
|
|
api/endpoints/__pycache__/quiz_answer.cpython-310.pyc
ADDED
Binary file (3.33 kB). View file
|
|
api/endpoints/__pycache__/school.cpython-310.pyc
ADDED
Binary file (1.81 kB). View file
|
|
api/endpoints/__pycache__/teacher_note.cpython-310.pyc
ADDED
Binary file (1.76 kB). View file
|
|
api/endpoints/__pycache__/two_fa.cpython-310.pyc
ADDED
Binary file (4.07 kB). View file
|
|
api/endpoints/__pycache__/users.cpython-310.pyc
ADDED
Binary file (5.93 kB). View file
|
|
api/endpoints/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (572 Bytes). View file
|
|
api/endpoints/assignment.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from locale import currency
|
2 |
+
from typing import Any, List
|
3 |
+
|
4 |
+
from hashlib import sha1
|
5 |
+
import os
|
6 |
+
import shutil
|
7 |
+
|
8 |
+
from fastapi import APIRouter, Depends, UploadFile, File
|
9 |
+
from sqlalchemy.orm import Session
|
10 |
+
from core.config import settings
|
11 |
+
|
12 |
+
from models import User
|
13 |
+
|
14 |
+
import aiofiles
|
15 |
+
|
16 |
+
from utils import deps
|
17 |
+
from cruds import crud_assignment, crud_group, crud_assignment_upload
|
18 |
+
from schemas import Assignment, AssignmentUpdate, AssignmentCreate
|
19 |
+
|
20 |
+
router = APIRouter()
|
21 |
+
|
22 |
+
ASSIGNMENT_ROUTE: str = "assignments"
|
23 |
+
|
24 |
+
|
25 |
+
@router.get("/", response_model=List[Assignment])
|
26 |
+
async def get_assignment(
|
27 |
+
db: Session = Depends(deps.get_db),
|
28 |
+
skip: int = 0,
|
29 |
+
limit: int = -1,
|
30 |
+
current_user: User = Depends(deps.get_current_active_user),
|
31 |
+
) -> Any:
|
32 |
+
|
33 |
+
if current_user.user_type == settings.UserType.STUDENT.value:
|
34 |
+
group = crud_group.get(db, id=current_user.group_id)
|
35 |
+
assignment = crud_assignment.get_quiz_by_group_id(db=db, group=group)
|
36 |
+
index = 0
|
37 |
+
for assig in assignment:
|
38 |
+
assignmentUpload = crud_assignment_upload.get_by_assignment_id(
|
39 |
+
db=db,
|
40 |
+
assignmentId=assig.id,
|
41 |
+
studentId=current_user.id,
|
42 |
+
)
|
43 |
+
|
44 |
+
if assignmentUpload:
|
45 |
+
assignment[index].exists = True
|
46 |
+
else:
|
47 |
+
assignment[index].exists = False
|
48 |
+
index += 1
|
49 |
+
|
50 |
+
return assignment
|
51 |
+
|
52 |
+
if current_user.user_type == settings.UserType.TEACHER.value:
|
53 |
+
return crud_assignment.get_quiz_by_instructor_id(db=db, user=current_user)
|
54 |
+
|
55 |
+
if current_user.user_type <= settings.UserType.ADMIN.value:
|
56 |
+
return crud_assignment.get_multi(db, skip=skip, limit=limit)
|
57 |
+
|
58 |
+
|
59 |
+
@router.post("/", response_model=Assignment)
|
60 |
+
async def create_assignment(
|
61 |
+
db: Session = Depends(deps.get_db),
|
62 |
+
*,
|
63 |
+
obj_in: AssignmentCreate,
|
64 |
+
current_user: User = Depends(deps.get_current_active_user),
|
65 |
+
) -> Any:
|
66 |
+
|
67 |
+
if obj_in.instructor:
|
68 |
+
if current_user.id not in obj_in.instructor:
|
69 |
+
obj_in.instructor.append(current_user.id)
|
70 |
+
else:
|
71 |
+
obj_in.instructor = [current_user.id]
|
72 |
+
|
73 |
+
assignment = crud_assignment.create(db, obj_in=obj_in)
|
74 |
+
return assignment
|
75 |
+
|
76 |
+
|
77 |
+
@router.post("/{id}/files/")
|
78 |
+
async def post_files(
|
79 |
+
db: Session = Depends(deps.get_db),
|
80 |
+
files: List[UploadFile] = File(...),
|
81 |
+
current_user=Depends(deps.get_current_active_teacher_or_above),
|
82 |
+
*,
|
83 |
+
id: int,
|
84 |
+
):
|
85 |
+
|
86 |
+
assignment = crud_assignment.get(db=db, id=id)
|
87 |
+
|
88 |
+
hashedAssignmentId = sha1(str(id).encode(encoding="UTF-8", errors="strict"))
|
89 |
+
|
90 |
+
FILE_ASSIGNMENT_PATH = os.path.join(
|
91 |
+
ASSIGNMENT_ROUTE,
|
92 |
+
hashedAssignmentId.hexdigest(),
|
93 |
+
)
|
94 |
+
|
95 |
+
FILE_PATH = os.path.join(
|
96 |
+
settings.UPLOAD_DIR_ROOT,
|
97 |
+
FILE_ASSIGNMENT_PATH,
|
98 |
+
)
|
99 |
+
|
100 |
+
if not os.path.exists(FILE_PATH):
|
101 |
+
os.makedirs(FILE_PATH)
|
102 |
+
|
103 |
+
if assignment.files:
|
104 |
+
assignmentFiles = assignment.files.copy()
|
105 |
+
fileIndex = len(assignment.files)
|
106 |
+
else:
|
107 |
+
assignmentFiles = []
|
108 |
+
fileIndex = 0
|
109 |
+
|
110 |
+
for file in files:
|
111 |
+
fileName, fileExtension = os.path.splitext(file.filename)
|
112 |
+
hashedFileName = sha1(
|
113 |
+
(fileName + str(fileIndex)).encode(encoding="UTF-8", errors="strict")
|
114 |
+
)
|
115 |
+
fileIndex = fileIndex + 1
|
116 |
+
filename = f"{FILE_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
117 |
+
async with aiofiles.open(filename, mode="wb") as f:
|
118 |
+
content = await file.read()
|
119 |
+
await f.write(content)
|
120 |
+
assignmentFiles.append(
|
121 |
+
{
|
122 |
+
"path": f"{FILE_ASSIGNMENT_PATH}/{hashedFileName.hexdigest()}{fileExtension}",
|
123 |
+
"name": file.filename,
|
124 |
+
}
|
125 |
+
)
|
126 |
+
|
127 |
+
obj_in = AssignmentUpdate(files=assignmentFiles)
|
128 |
+
updated = crud_assignment.update(db=db, db_obj=assignment, obj_in=obj_in)
|
129 |
+
|
130 |
+
return updated
|
131 |
+
|
132 |
+
|
133 |
+
@router.get("/{id}/", response_model=Assignment)
|
134 |
+
async def get_specific_assignment(
|
135 |
+
db: Session = Depends(deps.get_db),
|
136 |
+
current_user: User = Depends(deps.get_current_active_user),
|
137 |
+
*,
|
138 |
+
id: int,
|
139 |
+
) -> Any:
|
140 |
+
|
141 |
+
assignments = await get_assignment(db=db, current_user=current_user)
|
142 |
+
|
143 |
+
if assignments:
|
144 |
+
for assignment in assignments:
|
145 |
+
if assignment.id == id:
|
146 |
+
return assignment
|
147 |
+
|
148 |
+
|
149 |
+
@router.delete("/{id}/")
|
150 |
+
async def delete_assignment(
|
151 |
+
db: Session = Depends(deps.get_db),
|
152 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
153 |
+
*,
|
154 |
+
id: int,
|
155 |
+
) -> Any:
|
156 |
+
assignment = await get_specific_assignment(db=db, current_user=current_user, id=id)
|
157 |
+
|
158 |
+
if not assignment:
|
159 |
+
return {"msg": "assignment not found"}
|
160 |
+
|
161 |
+
deleted = crud_assignment.remove(db=db, id=assignment.id)
|
162 |
+
if deleted:
|
163 |
+
hashedAssignmentId = sha1(
|
164 |
+
str(assignment.id).encode(encoding="UTF-8", errors="strict")
|
165 |
+
)
|
166 |
+
FILE_ASSIGNMENT_PATH = os.path.join(
|
167 |
+
ASSIGNMENT_ROUTE,
|
168 |
+
hashedAssignmentId.hexdigest(),
|
169 |
+
)
|
170 |
+
|
171 |
+
FILE_PATH = os.path.join(
|
172 |
+
settings.UPLOAD_DIR_ROOT,
|
173 |
+
FILE_ASSIGNMENT_PATH,
|
174 |
+
)
|
175 |
+
|
176 |
+
if os.path.exists(FILE_PATH):
|
177 |
+
shutil.rmtree(FILE_PATH)
|
178 |
+
|
179 |
+
return {"msg": "delete success"}
|
180 |
+
|
181 |
+
|
182 |
+
@router.put("/{id}", response_model=Assignment)
|
183 |
+
async def update_assignment(
|
184 |
+
db: Session = Depends(deps.get_db), *, id: int, obj_in: AssignmentUpdate
|
185 |
+
) -> Any:
|
186 |
+
assignment = crud_assignment.get(db, id)
|
187 |
+
assignment = crud_assignment.update(db, db_obj=assignment, obj_in=obj_in)
|
188 |
+
return assignment
|
api/endpoints/assignment_upload.py
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
3 |
+
from sqlalchemy.orm import Session
|
4 |
+
from models import User
|
5 |
+
from utils import deps
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
from cruds import crud_assignment_upload, crud_assignment
|
8 |
+
from schemas import (
|
9 |
+
AssignmentUpload,
|
10 |
+
AssignmentUploadCreate,
|
11 |
+
AssignmentUploadUpdate,
|
12 |
+
AssignmentUploadwithName,
|
13 |
+
)
|
14 |
+
import os
|
15 |
+
from fastapi.responses import FileResponse
|
16 |
+
from hashlib import sha1
|
17 |
+
|
18 |
+
import aiofiles
|
19 |
+
|
20 |
+
import shutil
|
21 |
+
|
22 |
+
from typing import Any, Optional, List, Dict # noqa
|
23 |
+
from core.config import settings
|
24 |
+
|
25 |
+
|
26 |
+
router = APIRouter()
|
27 |
+
|
28 |
+
assignment_upload_ROUTE: str = "assignmentUpload"
|
29 |
+
|
30 |
+
|
31 |
+
@router.get("/")
|
32 |
+
async def get_assignments(
|
33 |
+
db: Session = Depends(deps.get_db),
|
34 |
+
*,
|
35 |
+
current_user: User = Depends(deps.get_current_active_user),
|
36 |
+
):
|
37 |
+
pass
|
38 |
+
|
39 |
+
|
40 |
+
@router.get("/{assignmentid}", response_model=AssignmentUpload)
|
41 |
+
async def get_assignment_upload(
|
42 |
+
db: Session = Depends(deps.get_db),
|
43 |
+
*,
|
44 |
+
assignmentid: int,
|
45 |
+
current_user: User = Depends(deps.get_current_active_user),
|
46 |
+
):
|
47 |
+
|
48 |
+
assignmentUpload = crud_assignment_upload.get_by_assignment_id(
|
49 |
+
db=db, assignmentId=assignmentid, studentId=current_user.id
|
50 |
+
)
|
51 |
+
|
52 |
+
if assignmentUpload:
|
53 |
+
return assignmentUpload
|
54 |
+
|
55 |
+
raise HTTPException(status_code=404, detail="Error ID: 147")
|
56 |
+
|
57 |
+
|
58 |
+
@router.get(
|
59 |
+
"/{assignmentid}/getUploadsAsTeacher", response_model=List[AssignmentUploadwithName]
|
60 |
+
)
|
61 |
+
async def get_assignment_upload_as_teacher(
|
62 |
+
db: Session = Depends(deps.get_db),
|
63 |
+
*,
|
64 |
+
assignmentid: int,
|
65 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
66 |
+
):
|
67 |
+
if current_user.assignments:
|
68 |
+
for assignment in current_user.assignments:
|
69 |
+
if assignment.id == assignmentid:
|
70 |
+
assignmentUpload = (
|
71 |
+
crud_assignment_upload.get_all_by_assignment_id_as_teacher(
|
72 |
+
db=db, assignmentId=assignmentid
|
73 |
+
)
|
74 |
+
)
|
75 |
+
if assignmentUpload:
|
76 |
+
return assignmentUpload
|
77 |
+
|
78 |
+
raise HTTPException(
|
79 |
+
status_code=404,
|
80 |
+
detail="Error ID: 148", # could not populate answer
|
81 |
+
)
|
82 |
+
|
83 |
+
|
84 |
+
@router.get("/{assignmentid}/exists")
|
85 |
+
async def check_existence(
|
86 |
+
db: Session = Depends(deps.get_db),
|
87 |
+
current_user: User = Depends(deps.get_current_active_user),
|
88 |
+
*,
|
89 |
+
assignmentid: int,
|
90 |
+
):
|
91 |
+
assignmentUpload = crud_assignment_upload.get_by_assignment_id(
|
92 |
+
db=db, assignmentId=assignmentid, studentId=current_user.id
|
93 |
+
)
|
94 |
+
|
95 |
+
if not assignmentUpload:
|
96 |
+
return {"exists": False}
|
97 |
+
else:
|
98 |
+
return {"exists": True}
|
99 |
+
|
100 |
+
|
101 |
+
@router.post("/{assignmentid}/upload")
|
102 |
+
async def post_files(
|
103 |
+
db: Session = Depends(deps.get_db),
|
104 |
+
files: List[UploadFile] = File(...),
|
105 |
+
current_user=Depends(deps.get_current_active_user),
|
106 |
+
*,
|
107 |
+
assignmentid: int,
|
108 |
+
):
|
109 |
+
|
110 |
+
hashedAssignmentId = sha1(
|
111 |
+
str(assignmentid).encode(encoding="UTF-8", errors="strict")
|
112 |
+
)
|
113 |
+
hashedUserId = sha1(str(current_user.id).encode(encoding="UTF-8", errors="strict"))
|
114 |
+
|
115 |
+
FILE_ASSIGNMENT_PATH = os.path.join(
|
116 |
+
assignment_upload_ROUTE,
|
117 |
+
hashedUserId.hexdigest(),
|
118 |
+
hashedAssignmentId.hexdigest(),
|
119 |
+
)
|
120 |
+
|
121 |
+
FILE_PATH = os.path.join(
|
122 |
+
settings.UPLOAD_DIR_ROOT,
|
123 |
+
FILE_ASSIGNMENT_PATH,
|
124 |
+
)
|
125 |
+
|
126 |
+
if os.path.exists(FILE_PATH):
|
127 |
+
shutil.rmtree(FILE_PATH)
|
128 |
+
|
129 |
+
if not os.path.exists(FILE_PATH):
|
130 |
+
os.makedirs(FILE_PATH)
|
131 |
+
|
132 |
+
fileIndex = 0
|
133 |
+
assignmentFiles = []
|
134 |
+
|
135 |
+
for file in files:
|
136 |
+
fileName, fileExtension = os.path.splitext(file.filename)
|
137 |
+
hashedFileName = sha1(
|
138 |
+
(fileName + str(fileIndex)).encode(encoding="UTF-8", errors="strict")
|
139 |
+
)
|
140 |
+
fileIndex = fileIndex + 1
|
141 |
+
filename = f"{FILE_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
142 |
+
async with aiofiles.open(filename, mode="wb") as f:
|
143 |
+
content = await file.read()
|
144 |
+
await f.write(content)
|
145 |
+
assignmentFiles.append(
|
146 |
+
{
|
147 |
+
"path": f"{FILE_ASSIGNMENT_PATH}/{hashedFileName.hexdigest()}{fileExtension}",
|
148 |
+
"name": file.filename,
|
149 |
+
}
|
150 |
+
)
|
151 |
+
|
152 |
+
assignmentUpload = crud_assignment_upload.get_by_assignment_id(
|
153 |
+
db=db, assignmentId=assignmentid, studentId=current_user.id
|
154 |
+
)
|
155 |
+
|
156 |
+
if assignmentUpload:
|
157 |
+
db_obj = assignmentUpload
|
158 |
+
obj_in = AssignmentUploadUpdate(
|
159 |
+
files=assignmentFiles,
|
160 |
+
submission_date=datetime.utcnow(),
|
161 |
+
marks_obtained=None,
|
162 |
+
)
|
163 |
+
assignmentUploadX = crud_assignment_upload.update(
|
164 |
+
db=db, db_obj=db_obj, obj_in=obj_in
|
165 |
+
)
|
166 |
+
else:
|
167 |
+
obj_in = AssignmentUploadCreate(
|
168 |
+
files=assignmentFiles,
|
169 |
+
assignment_id=assignmentid,
|
170 |
+
student_id=current_user.id,
|
171 |
+
submission_date=datetime.utcnow(),
|
172 |
+
marks_obtained=None,
|
173 |
+
)
|
174 |
+
assignmentUploadX = crud_assignment_upload.create(db=db, obj_in=obj_in)
|
175 |
+
|
176 |
+
return assignmentUploadX
|
177 |
+
|
178 |
+
|
179 |
+
@router.delete("/{assignmentid}/files")
|
180 |
+
async def post_files(
|
181 |
+
db: Session = Depends(deps.get_db),
|
182 |
+
current_user=Depends(deps.get_current_active_user),
|
183 |
+
*,
|
184 |
+
assignmentid: int,
|
185 |
+
):
|
186 |
+
|
187 |
+
hashedAssignmentId = sha1(
|
188 |
+
str(assignmentid).encode(encoding="UTF-8", errors="strict")
|
189 |
+
)
|
190 |
+
hashedUserId = sha1(str(current_user.id).encode(encoding="UTF-8", errors="strict"))
|
191 |
+
|
192 |
+
FILE_ASSIGNMENT_PATH = os.path.join(
|
193 |
+
assignment_upload_ROUTE,
|
194 |
+
hashedUserId.hexdigest(),
|
195 |
+
hashedAssignmentId.hexdigest(),
|
196 |
+
)
|
197 |
+
|
198 |
+
FILE_PATH = os.path.join(
|
199 |
+
settings.UPLOAD_DIR_ROOT,
|
200 |
+
FILE_ASSIGNMENT_PATH,
|
201 |
+
)
|
202 |
+
|
203 |
+
if os.path.exists(FILE_PATH):
|
204 |
+
shutil.rmtree(FILE_PATH)
|
205 |
+
|
206 |
+
assignmentUpload = crud_assignment_upload.get_by_assignment_id(
|
207 |
+
db=db, assignmentId=assignmentid, studentId=current_user.id
|
208 |
+
)
|
209 |
+
|
210 |
+
if assignmentUpload:
|
211 |
+
crud_assignment_upload.remove(db=db, id=assignmentUpload.id)
|
212 |
+
return {"message": "Success"}
|
213 |
+
|
214 |
+
raise HTTPException(status_code=404, detail="Error ID: 149")
|
215 |
+
|
216 |
+
|
217 |
+
@router.post("/{assignmentuploadid}/mark")
|
218 |
+
async def post_files(
|
219 |
+
db: Session = Depends(deps.get_db),
|
220 |
+
current_user=Depends(deps.get_current_active_teacher_or_above),
|
221 |
+
*,
|
222 |
+
assignmentuploadid: int,
|
223 |
+
marks_obtained: int,
|
224 |
+
):
|
225 |
+
|
226 |
+
assignmentUpload = crud_assignment_upload.get(db=db, id=assignmentuploadid)
|
227 |
+
|
228 |
+
if assignmentUpload:
|
229 |
+
obj_in = AssignmentUploadUpdate(marks_obtained=marks_obtained)
|
230 |
+
db_obj = assignmentUpload
|
231 |
+
updated = crud_assignment_upload.update(db=db, db_obj=db_obj, obj_in=obj_in)
|
232 |
+
|
233 |
+
return updated
|
234 |
+
|
235 |
+
raise HTTPException(status_code=404, detail="Error ID: 150")
|
api/endpoints/auth.py
ADDED
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from typing import Any, List, Optional
|
4 |
+
|
5 |
+
import aiofiles
|
6 |
+
from fastapi import APIRouter, Body
|
7 |
+
from fastapi import Cookie as ReqCookie
|
8 |
+
from fastapi import Depends, File, HTTPException, Request, UploadFile, Form
|
9 |
+
from fastapi.params import Cookie
|
10 |
+
from sqlalchemy.orm import Session
|
11 |
+
from sqlalchemy.sql.expression import update
|
12 |
+
from sqlalchemy.sql.functions import current_user
|
13 |
+
from starlette.responses import JSONResponse, Response
|
14 |
+
from starlette.status import HTTP_401_UNAUTHORIZED
|
15 |
+
|
16 |
+
import cruds
|
17 |
+
import models
|
18 |
+
import schemas
|
19 |
+
from core import throttle
|
20 |
+
from core.config import settings
|
21 |
+
from core.db import redis_session_client
|
22 |
+
from core.security import (
|
23 |
+
create_sesssion_token,
|
24 |
+
get_password_hash,
|
25 |
+
create_2fa_temp_token,
|
26 |
+
create_passwordless_create_token,
|
27 |
+
authorize_passwordless_token,
|
28 |
+
verify_passwordless_token,
|
29 |
+
)
|
30 |
+
from cruds import group
|
31 |
+
from schemas.user import UserUpdate, VerifyUser
|
32 |
+
from utils import deps
|
33 |
+
from utils.utils import (
|
34 |
+
expire_web_session,
|
35 |
+
generate_password_reset_token,
|
36 |
+
send_reset_password_email,
|
37 |
+
send_verification_email,
|
38 |
+
verify_password_reset_token,
|
39 |
+
verify_user_verify_token,
|
40 |
+
)
|
41 |
+
|
42 |
+
router = APIRouter()
|
43 |
+
|
44 |
+
|
45 |
+
@router.post(
|
46 |
+
"/web/",
|
47 |
+
response_model=Optional[schemas.user.UserLoginReturn],
|
48 |
+
response_model_exclude_none=True,
|
49 |
+
)
|
50 |
+
async def login_web_session(
|
51 |
+
db: Session = Depends(deps.get_db),
|
52 |
+
*,
|
53 |
+
form_data: schemas.LoginData,
|
54 |
+
request: Request,
|
55 |
+
response: Response,
|
56 |
+
) -> Any:
|
57 |
+
if not form_data.username:
|
58 |
+
form_data.username = form_data.email
|
59 |
+
|
60 |
+
user = cruds.crud_user.authenticate(
|
61 |
+
db, email=form_data.username, password=form_data.password
|
62 |
+
)
|
63 |
+
|
64 |
+
if not user:
|
65 |
+
raise HTTPException(
|
66 |
+
status_code=401, detail="Error ID: 111"
|
67 |
+
) # Incorrect email or password
|
68 |
+
elif not user.is_active:
|
69 |
+
raise HTTPException(
|
70 |
+
status_code=401, detail="Error ID: 112") # Inactive user
|
71 |
+
|
72 |
+
if user.two_fa_secret:
|
73 |
+
temp_token = await create_2fa_temp_token(user, form_data.remember_me)
|
74 |
+
response.set_cookie("temp_session", temp_token, httponly=True)
|
75 |
+
return {
|
76 |
+
"msg": "2FA required before proceeding!",
|
77 |
+
"two_fa_required": True,
|
78 |
+
"user": None,
|
79 |
+
}
|
80 |
+
else:
|
81 |
+
session_token = await create_sesssion_token(
|
82 |
+
user, form_data.remember_me, request
|
83 |
+
)
|
84 |
+
response.set_cookie("session", session_token, httponly=True)
|
85 |
+
return {
|
86 |
+
"msg": "Logged in successfully!",
|
87 |
+
"user": user,
|
88 |
+
"two_fa_required": False,
|
89 |
+
}
|
90 |
+
|
91 |
+
|
92 |
+
@router.get("/password-less/create")
|
93 |
+
async def generate_passwordless_login_token(
|
94 |
+
db: Session = Depends(deps.get_db),
|
95 |
+
):
|
96 |
+
token = await create_passwordless_create_token()
|
97 |
+
|
98 |
+
return {"token": token}
|
99 |
+
|
100 |
+
|
101 |
+
@router.post("/password-less/authorize")
|
102 |
+
async def authorize_passwordless_login(
|
103 |
+
db: Session = Depends(deps.get_db),
|
104 |
+
current_user: models.User = Depends(deps.get_current_active_user),
|
105 |
+
token: str = Form(None),
|
106 |
+
):
|
107 |
+
_ = await authorize_passwordless_token(current_user, token)
|
108 |
+
|
109 |
+
return {"msg": "Success"}
|
110 |
+
|
111 |
+
|
112 |
+
@router.post(
|
113 |
+
"/password-less/verify",
|
114 |
+
response_model=Optional[schemas.user.UserLoginReturn],
|
115 |
+
response_model_exclude_none=True,
|
116 |
+
)
|
117 |
+
async def verify_passwordless_login(
|
118 |
+
response: Response,
|
119 |
+
request: Request,
|
120 |
+
db: Session = Depends(deps.get_db),
|
121 |
+
token: str = Form(None),
|
122 |
+
):
|
123 |
+
user_id = await verify_passwordless_token(token)
|
124 |
+
|
125 |
+
user = cruds.crud_user.get_by_id(db, id=user_id)
|
126 |
+
|
127 |
+
if not user:
|
128 |
+
raise HTTPException(
|
129 |
+
status_code=HTTP_401_UNAUTHORIZED, detail="Invalid user!")
|
130 |
+
|
131 |
+
session_token = await create_sesssion_token(user, True, request)
|
132 |
+
response.set_cookie("session", session_token, httponly=True)
|
133 |
+
return {"msg": "Logged in successfully!", "user": user, "two_fa_required": False}
|
134 |
+
|
135 |
+
|
136 |
+
@router.post("/signup/", response_model=schemas.Msg)
|
137 |
+
async def sign_up(
|
138 |
+
*,
|
139 |
+
db: Session = Depends(deps.get_db),
|
140 |
+
user_in: schemas.UserSignUp,
|
141 |
+
) -> Any:
|
142 |
+
if not settings.USERS_OPEN_REGISTRATION:
|
143 |
+
raise HTTPException(
|
144 |
+
status_code=403,
|
145 |
+
detail="Error ID: 129",
|
146 |
+
) # Open user registration is forbidden on this server
|
147 |
+
user = cruds.crud_user.get_by_email(db, email=user_in.email)
|
148 |
+
if user:
|
149 |
+
raise HTTPException(
|
150 |
+
status_code=400,
|
151 |
+
detail="Email is associated with another user!",
|
152 |
+
) # The user with this username already exists in the system
|
153 |
+
email_host = user_in.email[user_in.email.index("@") + 1:]
|
154 |
+
|
155 |
+
if email_host not in settings.ALLOWED_EMAIL_HOST:
|
156 |
+
raise HTTPException(
|
157 |
+
status_code=403,
|
158 |
+
# TODO: Reflected XSS test
|
159 |
+
detail=f"Email of host {email_host} not allowed!",
|
160 |
+
)
|
161 |
+
|
162 |
+
user = cruds.crud_user.create(
|
163 |
+
db, obj_in=schemas.UserCreate(**user_in.dict(), profile_pic="")
|
164 |
+
)
|
165 |
+
await send_verification_email(user=user)
|
166 |
+
return schemas.Msg(msg="Success")
|
167 |
+
|
168 |
+
|
169 |
+
@router.post("/resend-verification-email/")
|
170 |
+
async def resend_verification_email(
|
171 |
+
email: str,
|
172 |
+
current_user: models.User = Depends(deps.get_current_admin_or_above),
|
173 |
+
db: Session = Depends(deps.get_db),
|
174 |
+
):
|
175 |
+
user = cruds.crud_user.get_by_email(db=db, email=email)
|
176 |
+
if not user:
|
177 |
+
raise HTTPException(status_code="404", detail="User doesn't exist")
|
178 |
+
|
179 |
+
await send_verification_email(user)
|
180 |
+
return schemas.Msg(msg="Success")
|
181 |
+
|
182 |
+
|
183 |
+
@router.post("/change-password/")
|
184 |
+
async def change_password(
|
185 |
+
current_password: str = Body(...),
|
186 |
+
new_password: str = Body(...),
|
187 |
+
current_user: models.User = Depends(deps.get_current_user),
|
188 |
+
db: Session = Depends(deps.get_db),
|
189 |
+
) -> Any:
|
190 |
+
user = cruds.crud_user.authenticate(
|
191 |
+
db, email=current_user.email, password=current_password
|
192 |
+
)
|
193 |
+
|
194 |
+
if not user:
|
195 |
+
raise HTTPException(
|
196 |
+
status_code=403, detail="Error ID: 111"
|
197 |
+
) # Incorrect email or password
|
198 |
+
|
199 |
+
data = schemas.user.PasswordUpdate(
|
200 |
+
password=new_password,
|
201 |
+
)
|
202 |
+
|
203 |
+
cruds.crud_user.update(db=db, db_obj=current_user, obj_in=data)
|
204 |
+
|
205 |
+
|
206 |
+
@router.get("/active-sessions/", response_model=List[schemas.auth.ActiveSession])
|
207 |
+
async def get_active_sessions(
|
208 |
+
current_user: models.User = Depends(deps.get_current_user),
|
209 |
+
) -> Any:
|
210 |
+
active_sessions = json.loads(
|
211 |
+
await redis_session_client.client.get(
|
212 |
+
f"user_{current_user.id}_sessions", encoding="utf-8"
|
213 |
+
)
|
214 |
+
)
|
215 |
+
return active_sessions.get("sessions")
|
216 |
+
|
217 |
+
|
218 |
+
@router.get("/logout-all-sessions/")
|
219 |
+
async def logout_all_sessions(
|
220 |
+
current_user: models.User = Depends(deps.get_current_user),
|
221 |
+
) -> Any:
|
222 |
+
active_sessions = json.loads(
|
223 |
+
await redis_session_client.client.get(
|
224 |
+
f"user_{current_user.id}_sessions", encoding="utf-8"
|
225 |
+
)
|
226 |
+
)
|
227 |
+
|
228 |
+
for session in active_sessions.get("sessions"):
|
229 |
+
await redis_session_client.client.expire(session.get("token"), 0)
|
230 |
+
|
231 |
+
await redis_session_client.client.expire(f"user_{current_user.id}_sessions", 0)
|
232 |
+
|
233 |
+
resp = JSONResponse({"status": "success"})
|
234 |
+
resp.delete_cookie("session")
|
235 |
+
return resp
|
236 |
+
|
237 |
+
|
238 |
+
@router.post("/password-recovery/", response_model=schemas.Msg)
|
239 |
+
# @throttle.ip_throttle(rate=3, per=1 * 60 * 60)
|
240 |
+
# @throttle.ip_throttle(rate=1, per=20)
|
241 |
+
async def recover_password(
|
242 |
+
request: Request,
|
243 |
+
email: str,
|
244 |
+
db: Session = Depends(deps.get_db),
|
245 |
+
) -> Any:
|
246 |
+
"""
|
247 |
+
Password Recovery
|
248 |
+
"""
|
249 |
+
user = cruds.crud_user.get_by_email(db, email=email)
|
250 |
+
|
251 |
+
if not user:
|
252 |
+
raise HTTPException(
|
253 |
+
status_code=404,
|
254 |
+
detail="Error ID: 113",
|
255 |
+
) # The user with this username does not exist in the system.
|
256 |
+
await send_reset_password_email(user=user)
|
257 |
+
return {"msg": "Password recovery email sent"}
|
258 |
+
|
259 |
+
|
260 |
+
@router.post("/reset-password/", response_model=schemas.Msg)
|
261 |
+
async def reset_password(
|
262 |
+
request: Request,
|
263 |
+
token: str = Body(...),
|
264 |
+
new_password: str = Body(...),
|
265 |
+
db: Session = Depends(deps.get_db),
|
266 |
+
) -> Any:
|
267 |
+
"""
|
268 |
+
Reset password
|
269 |
+
"""
|
270 |
+
uid = await verify_password_reset_token(token)
|
271 |
+
user = cruds.crud_user.get_by_id(db, id=uid)
|
272 |
+
if not user:
|
273 |
+
raise HTTPException(
|
274 |
+
status_code=404,
|
275 |
+
detail="Error ID: 114",
|
276 |
+
) # The user with this username does not exist in the system.
|
277 |
+
elif not cruds.crud_user.is_active(user):
|
278 |
+
raise HTTPException(
|
279 |
+
status_code=400, detail="Error ID: 115") # Inactive user
|
280 |
+
hashed_password = get_password_hash(new_password)
|
281 |
+
user.hashed_password = hashed_password
|
282 |
+
db.add(user)
|
283 |
+
db.commit()
|
284 |
+
return {"msg": "Password updated successfully"}
|
285 |
+
|
286 |
+
|
287 |
+
@router.post("/verify/", response_model=schemas.Msg)
|
288 |
+
async def verify_account(
|
289 |
+
token: str,
|
290 |
+
db: Session = Depends(deps.get_db),
|
291 |
+
) -> Any:
|
292 |
+
uid = await verify_user_verify_token(token)
|
293 |
+
user = cruds.crud_user.get_by_id(db, id=uid)
|
294 |
+
if not user:
|
295 |
+
raise HTTPException(
|
296 |
+
status_code=404,
|
297 |
+
detail="Error ID: 146",
|
298 |
+
) # The user with this username does not exist in the system.
|
299 |
+
cruds.crud_user.verify_user(db=db, db_obj=user)
|
300 |
+
return {"msg": "Verified successfully"}
|
301 |
+
|
302 |
+
|
303 |
+
@router.get("/logout/", response_model=schemas.Token)
|
304 |
+
async def session_logout(
|
305 |
+
session: str = ReqCookie(None),
|
306 |
+
) -> Any:
|
307 |
+
if not session:
|
308 |
+
raise HTTPException(status_code=401, detail="Invalid session token!")
|
309 |
+
await expire_web_session(session)
|
310 |
+
resp = JSONResponse({"status": "success"})
|
311 |
+
resp.delete_cookie("session")
|
312 |
+
return resp
|
313 |
+
|
314 |
+
|
315 |
+
@router.get("/thtest1")
|
316 |
+
@throttle.ip_throttle(rate=10, per=60)
|
317 |
+
async def throttle_test(
|
318 |
+
request: Request,
|
319 |
+
db: Session = Depends(deps.get_db),
|
320 |
+
current_user: models.User = Depends(deps.get_current_user),
|
321 |
+
):
|
322 |
+
return "Throttle test endpoint 1 Hello"
|
323 |
+
|
324 |
+
|
325 |
+
@router.get("/thtest2")
|
326 |
+
@throttle.user_throttle(rate=20, per=60)
|
327 |
+
async def throttle_test1(
|
328 |
+
request: Request,
|
329 |
+
db: Session = Depends(deps.get_db),
|
330 |
+
current_user: models.User = Depends(deps.get_current_user),
|
331 |
+
):
|
332 |
+
return "Throttle test endpoint 2"
|
api/endpoints/class_session.py
ADDED
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from hashlib import sha256
|
4 |
+
from typing import Any, List
|
5 |
+
|
6 |
+
import aiofiles
|
7 |
+
from fastapi import (
|
8 |
+
APIRouter,
|
9 |
+
Depends,
|
10 |
+
FastAPI,
|
11 |
+
File,
|
12 |
+
Form,
|
13 |
+
HTTPException,
|
14 |
+
UploadFile,
|
15 |
+
WebSocket,
|
16 |
+
WebSocketDisconnect,
|
17 |
+
)
|
18 |
+
from fastapi.encoders import jsonable_encoder
|
19 |
+
from fastapi.responses import FileResponse, HTMLResponse
|
20 |
+
from sqlalchemy.orm import Session
|
21 |
+
from starlette.status import HTTP_406_NOT_ACCEPTABLE
|
22 |
+
|
23 |
+
from core.config import settings
|
24 |
+
from core.security import get_uid_hash
|
25 |
+
from core.websocket import ws, ChatMessageTypes
|
26 |
+
from cruds import crud_class_session, crud_file, crud_user
|
27 |
+
from forms.class_session import ClassSessionCreateForm
|
28 |
+
from models import ClassSession as ClassSessionModel
|
29 |
+
from models import File as FileModel
|
30 |
+
from schemas.class_session import (
|
31 |
+
ClassSession,
|
32 |
+
ClassSessionCreate,
|
33 |
+
ClassSessionReturn,
|
34 |
+
ClassSessionUpdate,
|
35 |
+
ClassSessionTeacherReturn,
|
36 |
+
AttendanceUpdate,
|
37 |
+
ParticipantOfClassSession,
|
38 |
+
)
|
39 |
+
from schemas.file import FileCreate
|
40 |
+
from schemas.group import GroupStudentReturn
|
41 |
+
from utils import deps
|
42 |
+
from utils.deps import get_current_active_teacher_or_above, get_current_active_user, get_current_active_ws_user
|
43 |
+
import datetime
|
44 |
+
import cruds
|
45 |
+
|
46 |
+
router = APIRouter()
|
47 |
+
|
48 |
+
|
49 |
+
@router.get("/", response_model=List[ClassSessionReturn])
|
50 |
+
def get_class_session(
|
51 |
+
db: Session = Depends(deps.get_db),
|
52 |
+
user=Depends(get_current_active_user),
|
53 |
+
skip: int = 0,
|
54 |
+
limit: int = 100,
|
55 |
+
) -> Any:
|
56 |
+
class_sessions = crud_class_session.get_user_class_session(db, user=user)
|
57 |
+
return class_sessions
|
58 |
+
|
59 |
+
|
60 |
+
@router.get("/active", response_model=List[ClassSessionReturn])
|
61 |
+
def get_active_class_session(
|
62 |
+
db: Session = Depends(deps.get_db),
|
63 |
+
user=Depends(get_current_active_user),
|
64 |
+
) -> Any:
|
65 |
+
class_sessions = crud_class_session.get_user_class_session(db, user=user)
|
66 |
+
active_class_sessions = []
|
67 |
+
|
68 |
+
for class_session in class_sessions:
|
69 |
+
if(class_session.start_time < datetime.datetime.now() and class_session.end_time > datetime.datetime.now()):
|
70 |
+
active_class_sessions.append(class_session)
|
71 |
+
|
72 |
+
return active_class_sessions
|
73 |
+
|
74 |
+
|
75 |
+
@router.post("/", response_model=ClassSession)
|
76 |
+
async def create_class_session(
|
77 |
+
db: Session = Depends(deps.get_db),
|
78 |
+
user=Depends(get_current_active_teacher_or_above),
|
79 |
+
*,
|
80 |
+
form: ClassSessionCreateForm = Depends(),
|
81 |
+
) -> Any:
|
82 |
+
course_id = None
|
83 |
+
for item in user.teacher_group:
|
84 |
+
course_id = item.course.id if item.group.id == form.group else course_id
|
85 |
+
|
86 |
+
if(course_id == None):
|
87 |
+
raise HTTPException(
|
88 |
+
status_code=HTTP_406_NOT_ACCEPTABLE, detail="Invalid group id!")
|
89 |
+
|
90 |
+
data = ClassSessionCreate(
|
91 |
+
start_time=form.start_time,
|
92 |
+
end_time=form.end_time,
|
93 |
+
instructor=[user.id]+(form.instructor or []),
|
94 |
+
description=form.description,
|
95 |
+
group_id=form.group,
|
96 |
+
course_id=course_id,
|
97 |
+
)
|
98 |
+
|
99 |
+
class_session = crud_class_session.create(db, obj_in=data)
|
100 |
+
|
101 |
+
hasher = sha256()
|
102 |
+
hasher.update(bytes(f"{class_session.id}_{settings.SECRET_KEY}", "utf-8"))
|
103 |
+
db_folder_path = os.path.join("class_files", hasher.hexdigest())
|
104 |
+
folder_path = os.path.join(settings.UPLOAD_DIR_ROOT, db_folder_path)
|
105 |
+
|
106 |
+
if not os.path.exists(folder_path):
|
107 |
+
os.makedirs(folder_path)
|
108 |
+
|
109 |
+
if form.file:
|
110 |
+
for file in form.file:
|
111 |
+
file_path = os.path.join(folder_path, file.filename)
|
112 |
+
async with aiofiles.open(file_path, mode="wb") as f:
|
113 |
+
content = await file.read()
|
114 |
+
await f.write(content)
|
115 |
+
|
116 |
+
db.add(
|
117 |
+
FileModel(
|
118 |
+
name=file.filename,
|
119 |
+
path=db_folder_path,
|
120 |
+
file_type=file.content_type,
|
121 |
+
description=None,
|
122 |
+
class_session=class_session,
|
123 |
+
)
|
124 |
+
)
|
125 |
+
db.commit()
|
126 |
+
|
127 |
+
return class_session
|
128 |
+
|
129 |
+
|
130 |
+
@router.get("/{id}/", response_model=ClassSessionReturn)
|
131 |
+
def get_specific_class_session(
|
132 |
+
db: Session = Depends(deps.get_db),
|
133 |
+
user=Depends(get_current_active_user),
|
134 |
+
*,
|
135 |
+
id: int,
|
136 |
+
) -> Any:
|
137 |
+
class_session = crud_class_session.get_user_class_session(
|
138 |
+
db=db, user=user, id=id)
|
139 |
+
return class_session
|
140 |
+
|
141 |
+
|
142 |
+
@router.get("/{id}/participants", response_model=List[ParticipantOfClassSession])
|
143 |
+
def get_specific_class_session(
|
144 |
+
db: Session = Depends(deps.get_db),
|
145 |
+
current_user=Depends(get_current_active_user),
|
146 |
+
*,
|
147 |
+
id: int,
|
148 |
+
) -> Any:
|
149 |
+
class_session = crud_class_session.get(db, id)
|
150 |
+
|
151 |
+
group = cruds.crud_group.get(db, class_session.group_id)
|
152 |
+
|
153 |
+
participants = group.student + class_session.instructor
|
154 |
+
|
155 |
+
return participants
|
156 |
+
|
157 |
+
|
158 |
+
@router.get("/{id}/attendance", response_model=ClassSessionTeacherReturn)
|
159 |
+
def get_class_session_with_attendance(
|
160 |
+
db: Session = Depends(deps.get_db),
|
161 |
+
user=Depends(get_current_active_teacher_or_above),
|
162 |
+
*,
|
163 |
+
id: int,
|
164 |
+
) -> Any:
|
165 |
+
class_session = crud_class_session.get_user_class_session(
|
166 |
+
db=db, user=user, id=id)
|
167 |
+
return class_session
|
168 |
+
|
169 |
+
|
170 |
+
@router.put("/{id}/", response_model=ClassSession)
|
171 |
+
def update_class_session(
|
172 |
+
db: Session = Depends(deps.get_db), *, id: int, obj_in: ClassSessionUpdate
|
173 |
+
) -> Any:
|
174 |
+
class_session = crud_class_session.get(db, id)
|
175 |
+
class_session = crud_class_session.update(
|
176 |
+
db, db_obj=class_session, obj_in=obj_in)
|
177 |
+
return class_session
|
178 |
+
|
179 |
+
|
180 |
+
@router.put("/{class_id}/files")
|
181 |
+
async def update_class_session(
|
182 |
+
db: Session = Depends(deps.get_db),
|
183 |
+
*,
|
184 |
+
class_id: int,
|
185 |
+
files: List[UploadFile] = File(None),
|
186 |
+
) -> Any:
|
187 |
+
class_session = crud_class_session.get(db, class_id)
|
188 |
+
|
189 |
+
hasher = sha256()
|
190 |
+
hasher.update(bytes(f"{class_id}_{settings.SECRET_KEY}", "utf-8"))
|
191 |
+
db_folder_path = os.path.join("class_files", hasher.hexdigest())
|
192 |
+
folder_path = os.path.join(settings.UPLOAD_DIR_ROOT, db_folder_path)
|
193 |
+
|
194 |
+
if not os.path.exists(folder_path):
|
195 |
+
os.makedirs(folder_path)
|
196 |
+
|
197 |
+
for file in files:
|
198 |
+
file_path = os.path.join(folder_path, file.filename)
|
199 |
+
async with aiofiles.open(file_path, mode="wb") as f:
|
200 |
+
content = await file.read()
|
201 |
+
await f.write(content)
|
202 |
+
|
203 |
+
db.add(
|
204 |
+
FileModel(
|
205 |
+
name=file.filename,
|
206 |
+
path=db_folder_path,
|
207 |
+
file_type=file.content_type,
|
208 |
+
description=None,
|
209 |
+
class_session=class_session,
|
210 |
+
)
|
211 |
+
)
|
212 |
+
db.commit()
|
213 |
+
return {"msg": "success"}
|
214 |
+
|
215 |
+
|
216 |
+
@router.put("/{id}/attendance")
|
217 |
+
def attendance_of_class_session(
|
218 |
+
db: Session = Depends(deps.get_db),
|
219 |
+
*,
|
220 |
+
id: int,
|
221 |
+
obj_in: AttendanceUpdate,
|
222 |
+
current_teacher=Depends(get_current_active_teacher_or_above),
|
223 |
+
) -> Any:
|
224 |
+
class_session = crud_class_session.get_user_class_session(
|
225 |
+
db=db, user=current_teacher, id=id
|
226 |
+
)
|
227 |
+
if not class_session:
|
228 |
+
raise HTTPException(
|
229 |
+
status_code=403, detail="Class session access denied!")
|
230 |
+
class_session = crud_class_session.attendance_update(
|
231 |
+
db, db_obj=class_session, obj_in=obj_in
|
232 |
+
)
|
233 |
+
return {"msg": "success"}
|
234 |
+
|
235 |
+
|
236 |
+
# @router.post("/{id}/file/")
|
237 |
+
# async def create_upload_files(
|
238 |
+
# db: Session = Depends(deps.get_db),
|
239 |
+
# files: List[UploadFile] = File(...),
|
240 |
+
# current_teacher=Depends(
|
241 |
+
# get_current_active_teacher_or_above
|
242 |
+
# ), # FIXME : Get current user ?
|
243 |
+
# *,
|
244 |
+
# id: int,
|
245 |
+
# ):
|
246 |
+
# class_session = crud_class_session.get_user_class_session(
|
247 |
+
# db=db, user=current_teacher, id=id
|
248 |
+
# )
|
249 |
+
|
250 |
+
# if not class_session:
|
251 |
+
# raise HTTPException(status_code=403, detail="Error ID: 100") # Access denied!
|
252 |
+
|
253 |
+
# FILE_PATH = os.path.join("static", settings.UPLOAD_DIR_ROOT)
|
254 |
+
# working_directory = os.getcwd()
|
255 |
+
# FILE_PATH = os.path.join(working_directory, FILE_PATH)
|
256 |
+
|
257 |
+
# for file in files:
|
258 |
+
# filename = f"{FILE_PATH}/{id}/{file.filename}"
|
259 |
+
# async with aiofiles.open(filename, mode="wb") as f:
|
260 |
+
# content = await file.read()
|
261 |
+
# await f.write(content)
|
262 |
+
|
263 |
+
# obj_in = ClassSessionUpdate(file=[file.filename for file in files])
|
264 |
+
# crud_class_session.update(db=db, db_obj=class_session, obj_in=obj_in)
|
265 |
+
|
266 |
+
# return {"msg": "success"}
|
267 |
+
|
268 |
+
|
269 |
+
@router.websocket("/ws/{id}/")
|
270 |
+
async def websocket_endpoint(
|
271 |
+
db: Session = Depends(deps.get_db),
|
272 |
+
*,
|
273 |
+
websocket: WebSocket,
|
274 |
+
req_user=Depends(get_current_active_ws_user),
|
275 |
+
id: int,
|
276 |
+
):
|
277 |
+
user_id = req_user.id
|
278 |
+
class_session = crud_class_session.get_user_class_session(
|
279 |
+
db=db, user=req_user, id=id
|
280 |
+
)
|
281 |
+
if not class_session:
|
282 |
+
raise HTTPException(
|
283 |
+
status_code=403, detail="Error Code: 144"
|
284 |
+
) # User doesn't have access to classsession
|
285 |
+
await ws.connect(websocket=websocket, class_session_id=id, user_id=user_id)
|
286 |
+
try:
|
287 |
+
while True:
|
288 |
+
data = await websocket.receive_json()
|
289 |
+
if data.get("msg_type") == ChatMessageTypes.MESSAGE_HISTORY.value:
|
290 |
+
await ws.send_history(websocket, id)
|
291 |
+
else:
|
292 |
+
await ws.message(
|
293 |
+
websocket=websocket,
|
294 |
+
user_id=user_id,
|
295 |
+
class_session_id=id,
|
296 |
+
message=data.get("message"),
|
297 |
+
anon=data.get("anon"),
|
298 |
+
)
|
299 |
+
except WebSocketDisconnect:
|
300 |
+
await ws.disconnect(websocket, class_session_id=id, user_id=user_id)
|
api/endpoints/course.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from manage import crud
|
2 |
+
from typing import Any, List
|
3 |
+
|
4 |
+
from fastapi import APIRouter, Depends
|
5 |
+
from sqlalchemy.orm import Session
|
6 |
+
|
7 |
+
from utils import deps
|
8 |
+
from cruds import crud_course
|
9 |
+
from schemas.course import Course, CourseCreate, CourseUpdate
|
10 |
+
from core import settings
|
11 |
+
from models import User
|
12 |
+
from fastapi import HTTPException
|
13 |
+
|
14 |
+
router = APIRouter()
|
15 |
+
|
16 |
+
|
17 |
+
# get course, can be called by any user (1 through 4)
|
18 |
+
@router.get("/", response_model=List[Course])
|
19 |
+
def get_course(
|
20 |
+
current_user: User = Depends(deps.get_current_active_user),
|
21 |
+
db: Session = Depends(deps.get_db),
|
22 |
+
skip: int = 0,
|
23 |
+
limit: int = 100,
|
24 |
+
) -> Any:
|
25 |
+
course = crud_course.get_multi(db, skip=skip, limit=limit)
|
26 |
+
return course
|
27 |
+
|
28 |
+
|
29 |
+
# add a new course, only executed if the user is either a super admin or admin
|
30 |
+
@router.post("/")
|
31 |
+
def create_course(
|
32 |
+
db: Session = Depends(deps.get_db),
|
33 |
+
*,
|
34 |
+
obj_in: CourseCreate,
|
35 |
+
current_user: User = Depends(deps.get_current_admin_or_above),
|
36 |
+
) -> Any:
|
37 |
+
crud_course.create(db, obj_in=obj_in)
|
38 |
+
return {"status": "success"}
|
39 |
+
|
40 |
+
|
41 |
+
# get a specific course, can be called by any user (1 through 4)
|
42 |
+
@router.get("/{id}/", response_model=Course)
|
43 |
+
def get_specific_course(
|
44 |
+
current_user: User = Depends(deps.get_current_active_user),
|
45 |
+
db: Session = Depends(deps.get_db),
|
46 |
+
*,
|
47 |
+
id: int,
|
48 |
+
) -> Any:
|
49 |
+
course = crud_course.get(db, id)
|
50 |
+
return course
|
51 |
+
|
52 |
+
|
53 |
+
# update a specific user, can be called by only admin and superadmin
|
54 |
+
@router.put("/{id}/")
|
55 |
+
def update_course(
|
56 |
+
db: Session = Depends(deps.get_db),
|
57 |
+
*,
|
58 |
+
id: int,
|
59 |
+
obj_in: CourseUpdate,
|
60 |
+
current_user: User = Depends(deps.get_current_admin_or_above),
|
61 |
+
) -> Any:
|
62 |
+
course = crud_course.get(db, id)
|
63 |
+
crud_course.update(db, db_obj=course, obj_in=obj_in)
|
64 |
+
return {"status": "success"}
|
65 |
+
|
66 |
+
|
67 |
+
@router.delete("/{course_id}/")
|
68 |
+
async def delete_course(
|
69 |
+
db: Session = Depends(deps.get_db),
|
70 |
+
user=Depends(deps.get_current_admin_or_above),
|
71 |
+
*,
|
72 |
+
course_id: int,
|
73 |
+
):
|
74 |
+
crud_course.remove(db=db, id=course_id)
|
75 |
+
return {"msg": "success"}
|
api/endpoints/department.py
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
|
6 |
+
from utils import deps
|
7 |
+
from cruds import crud_department
|
8 |
+
from schemas import Department, DepartmentUpdate, Course
|
9 |
+
from models import User
|
10 |
+
from fastapi import HTTPException
|
11 |
+
from core import settings
|
12 |
+
|
13 |
+
router = APIRouter()
|
14 |
+
|
15 |
+
# get all Departments, can be called by all users (1 through 4)
|
16 |
+
@router.get("/", response_model=List[Department])
|
17 |
+
def get_department(
|
18 |
+
db: Session = Depends(deps.get_db),
|
19 |
+
skip: int = 0,
|
20 |
+
limit: int = 100,
|
21 |
+
current_user: User = Depends(deps.get_current_active_user),
|
22 |
+
) -> Any:
|
23 |
+
department = crud_department.get_multi(db, skip=skip, limit=limit)
|
24 |
+
return department
|
25 |
+
|
26 |
+
|
27 |
+
# create a new deparment, can be only created by admin and superadmin
|
28 |
+
@router.post("/", response_model=Department)
|
29 |
+
def create_department(
|
30 |
+
db: Session = Depends(deps.get_db),
|
31 |
+
*,
|
32 |
+
obj_in: DepartmentUpdate,
|
33 |
+
current_user: User = Depends(deps.get_current_active_user),
|
34 |
+
) -> Any:
|
35 |
+
if current_user.user_type > settings.UserType.ADMIN.value:
|
36 |
+
raise HTTPException(
|
37 |
+
status_code=403, detail="Error ID: 104"
|
38 |
+
) # user has no authorization for creating departments
|
39 |
+
else:
|
40 |
+
department = crud_department.create(db, obj_in=obj_in)
|
41 |
+
return department
|
42 |
+
|
43 |
+
|
44 |
+
# get a specific department, can be called by all user types (1 through 4)
|
45 |
+
@router.get("/{id}/", response_model=Department)
|
46 |
+
def get_specific_department(
|
47 |
+
db: Session = Depends(deps.get_db),
|
48 |
+
*,
|
49 |
+
id: int,
|
50 |
+
current_user: User = Depends(deps.get_current_active_user),
|
51 |
+
) -> Any:
|
52 |
+
department = crud_department.get(db, id)
|
53 |
+
return department
|
54 |
+
|
55 |
+
|
56 |
+
# update a specific department, can be called by only superadmin and admin
|
57 |
+
@router.put("/{id}/")
|
58 |
+
def update_department(
|
59 |
+
db: Session = Depends(deps.get_db),
|
60 |
+
*,
|
61 |
+
id: int,
|
62 |
+
obj_in: DepartmentUpdate,
|
63 |
+
current_user: User = Depends(deps.get_current_active_user),
|
64 |
+
) -> Any:
|
65 |
+
if current_user.user_type > settings.UserType.ADMIN.value:
|
66 |
+
raise HTTPException(
|
67 |
+
status_code=403, detail="Error ID: 105"
|
68 |
+
) # user has no authorization for updating departments
|
69 |
+
else:
|
70 |
+
department = crud_department.get(db, id)
|
71 |
+
crud_department.update(db, db_obj=department, obj_in=obj_in)
|
72 |
+
return {"status": "success"}
|
73 |
+
|
74 |
+
|
75 |
+
@router.get("/{id}/courses/", response_model=List[Course])
|
76 |
+
def get_department_course(
|
77 |
+
db: Session = Depends(deps.get_db),
|
78 |
+
*,
|
79 |
+
id: int,
|
80 |
+
current_user: User = Depends(deps.get_current_admin_or_above),
|
81 |
+
) -> Any:
|
82 |
+
department = crud_department.get(db, id)
|
83 |
+
courses = department.courses
|
84 |
+
return courses
|
85 |
+
|
86 |
+
|
87 |
+
@router.delete("/{department_id}/")
|
88 |
+
async def delete_department(
|
89 |
+
db: Session = Depends(deps.get_db),
|
90 |
+
user=Depends(deps.get_current_admin_or_above),
|
91 |
+
*,
|
92 |
+
department_id: int,
|
93 |
+
):
|
94 |
+
crud_department.remove(db=db, id=department_id)
|
95 |
+
return {"msg": "success"}
|
api/endpoints/group.py
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from schemas.group import GroupReturn
|
2 |
+
from typing import Any, List
|
3 |
+
|
4 |
+
from fastapi import APIRouter, Depends
|
5 |
+
from fastapi.encoders import jsonable_encoder
|
6 |
+
from sqlalchemy.orm import Session
|
7 |
+
|
8 |
+
from utils import deps
|
9 |
+
from cruds import crud_group, crud_department, crud_course
|
10 |
+
from schemas.group import (
|
11 |
+
Group,
|
12 |
+
GroupUpdate,
|
13 |
+
GroupCreate,
|
14 |
+
GroupStudentReturn,
|
15 |
+
GroupWithProgram,
|
16 |
+
)
|
17 |
+
from models import User
|
18 |
+
from core import settings
|
19 |
+
from fastapi import HTTPException
|
20 |
+
|
21 |
+
router = APIRouter()
|
22 |
+
|
23 |
+
# get group:
|
24 |
+
# can be called by student to get their group,
|
25 |
+
# can be called by teacher to get the group under their depart
|
26 |
+
# can be called by admin and super admin to get all the departs
|
27 |
+
@router.get("/", response_model=List[GroupWithProgram])
|
28 |
+
async def get_group(
|
29 |
+
db: Session = Depends(deps.get_db),
|
30 |
+
skip: int = 0,
|
31 |
+
limit: int = 100,
|
32 |
+
current_user: User = Depends(deps.get_current_active_user),
|
33 |
+
) -> Any:
|
34 |
+
if current_user.user_type == settings.UserType.STUDENT.value:
|
35 |
+
got_group = crud_group.get(db, current_user.group_id)
|
36 |
+
group = []
|
37 |
+
group.append(got_group)
|
38 |
+
return group
|
39 |
+
|
40 |
+
if current_user.user_type == settings.UserType.TEACHER.value:
|
41 |
+
return [
|
42 |
+
teacher_group_link.group
|
43 |
+
for teacher_group_link in current_user.teacher_group
|
44 |
+
]
|
45 |
+
|
46 |
+
if current_user.user_type <= settings.UserType.ADMIN.value:
|
47 |
+
group = crud_group.get_multi(db, skip=skip, limit=limit)
|
48 |
+
return group
|
49 |
+
|
50 |
+
|
51 |
+
# create new group, can be done by only admin and super admin
|
52 |
+
@router.post("/", response_model=Group)
|
53 |
+
async def create_group(
|
54 |
+
db: Session = Depends(deps.get_db),
|
55 |
+
*,
|
56 |
+
obj_in: GroupCreate,
|
57 |
+
current_user: User = Depends(deps.get_current_admin_or_above),
|
58 |
+
) -> Any:
|
59 |
+
return crud_group.create(db, obj_in=obj_in)
|
60 |
+
|
61 |
+
|
62 |
+
# get a specific group by id
|
63 |
+
# student: cannot get by id, can get their own group by directly calling "/"
|
64 |
+
# teacher: can get a specific group only if it exists in their groups_list
|
65 |
+
# superadmin and admin, no restriction, can get any group by id
|
66 |
+
@router.get("/{id}", response_model=Group, summary="Get specific group")
|
67 |
+
@router.get(
|
68 |
+
"/{id}/student/",
|
69 |
+
response_model=GroupStudentReturn,
|
70 |
+
summary="Get students of specific group",
|
71 |
+
)
|
72 |
+
async def get_specific_group(
|
73 |
+
db: Session = Depends(deps.get_db),
|
74 |
+
*,
|
75 |
+
id: int,
|
76 |
+
current_user: User = Depends(deps.get_current_active_user),
|
77 |
+
) -> Any:
|
78 |
+
if not current_user:
|
79 |
+
raise HTTPException(status_code=404, detail="Error ID: 107") # user not found!
|
80 |
+
|
81 |
+
if current_user.user_type == settings.UserType.STUDENT.value:
|
82 |
+
if current_user.group_id == id:
|
83 |
+
return crud_group.get(db, id=id)
|
84 |
+
else:
|
85 |
+
raise HTTPException(
|
86 |
+
status_code=403,
|
87 |
+
detail="Error ID: 108",
|
88 |
+
) # user has no authorization to access this group
|
89 |
+
|
90 |
+
if current_user.user_type == settings.UserType.TEACHER.value:
|
91 |
+
for group in current_user.teacher_group:
|
92 |
+
if group.teacher_id == current_user.id:
|
93 |
+
return group.group
|
94 |
+
raise HTTPException(
|
95 |
+
status_code=403,
|
96 |
+
detail="Error ID: 109",
|
97 |
+
) # user has no authorization to access this group
|
98 |
+
|
99 |
+
if current_user.user_type >= settings.UserType.ADMIN.value:
|
100 |
+
group = crud_group.get(db, id)
|
101 |
+
return group
|
102 |
+
|
103 |
+
|
104 |
+
# update group, can be called by only the superadmin and admin
|
105 |
+
@router.put("/{id}", response_model=GroupUpdate)
|
106 |
+
async def update_group(
|
107 |
+
db: Session = Depends(deps.get_db),
|
108 |
+
*,
|
109 |
+
id: int,
|
110 |
+
obj_in: GroupUpdate,
|
111 |
+
current_user: User = Depends(deps.get_current_active_user),
|
112 |
+
) -> Any:
|
113 |
+
|
114 |
+
if current_user.user_type >= settings.UserType.TEACHER.value:
|
115 |
+
raise HTTPException(
|
116 |
+
status_code=403,
|
117 |
+
detail="Error ID: 110",
|
118 |
+
) # user has no authorization for updating groups
|
119 |
+
else:
|
120 |
+
group = crud_group.get(db, id)
|
121 |
+
crud_group.update(db, db_obj=group, obj_in=obj_in)
|
122 |
+
return {"status": "success"}
|
123 |
+
|
124 |
+
|
125 |
+
@router.get("/all/")
|
126 |
+
async def get_all_groups(
|
127 |
+
db: Session = Depends(deps.get_db),
|
128 |
+
) -> Any:
|
129 |
+
group = crud_group.get_multi(db, limit=-1)
|
130 |
+
return group
|
api/endpoints/personal_note.py
ADDED
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
|
6 |
+
from utils import deps
|
7 |
+
from cruds import crud_personal_note
|
8 |
+
from schemas import PersonalNote, PersonalNoteUpdate, PersonalNoteCreate
|
9 |
+
from models import User
|
10 |
+
from core import settings
|
11 |
+
from fastapi import HTTPException
|
12 |
+
|
13 |
+
router = APIRouter()
|
14 |
+
|
15 |
+
|
16 |
+
# get personal note:
|
17 |
+
# student: get only theirs
|
18 |
+
# teacher: get only theirs
|
19 |
+
# admin: none
|
20 |
+
# super admin: all
|
21 |
+
@router.get("/", response_model=List[PersonalNote])
|
22 |
+
def get_personal_note(
|
23 |
+
db: Session = Depends(deps.get_db),
|
24 |
+
skip: int = 0,
|
25 |
+
limit: int = 100,
|
26 |
+
current_user: User = Depends(deps.get_current_active_user),
|
27 |
+
) -> Any:
|
28 |
+
|
29 |
+
if not current_user:
|
30 |
+
# user not found!
|
31 |
+
raise HTTPException(status_code=404, detail="Error ID: 116")
|
32 |
+
|
33 |
+
if current_user.user_type >= settings.UserType.TEACHER.value:
|
34 |
+
personal_note_list = []
|
35 |
+
personal_notes = current_user.personalnote
|
36 |
+
for note in personal_notes:
|
37 |
+
personal_note = crud_personal_note.get(db, id=note.id)
|
38 |
+
personal_note_list.append(personal_note)
|
39 |
+
return personal_note_list
|
40 |
+
|
41 |
+
if current_user.user_type == settings.UserType.ADMIN.value:
|
42 |
+
raise HTTPException(
|
43 |
+
status_code=403,
|
44 |
+
detail="Error ID: 117",
|
45 |
+
) # user has no authorization for retrieving personal notes, cause they personal fam!
|
46 |
+
|
47 |
+
if current_user.user_type == settings.UserType.SUPERADMIN.value:
|
48 |
+
personal_note = crud_personal_note.get_multi(db, skip=skip, limit=limit)
|
49 |
+
return personal_note
|
50 |
+
|
51 |
+
|
52 |
+
# Create new personal note
|
53 |
+
# student: can create only theirs
|
54 |
+
# teacher: can create only theirs
|
55 |
+
# admin: no create previlege
|
56 |
+
# superadmin: can create all
|
57 |
+
@router.post("/", response_model=PersonalNote)
|
58 |
+
def create_personal_note(
|
59 |
+
db: Session = Depends(deps.get_db),
|
60 |
+
*,
|
61 |
+
obj_in: PersonalNoteCreate,
|
62 |
+
current_user: User = Depends(deps.get_current_active_user),
|
63 |
+
) -> Any:
|
64 |
+
if not current_user:
|
65 |
+
# user not found!
|
66 |
+
raise HTTPException(status_code=404, detail="Error ID: 119")
|
67 |
+
|
68 |
+
if current_user.user_type >= settings.UserType.TEACHER.value:
|
69 |
+
if obj_in.user_id != current_user.id:
|
70 |
+
raise HTTPException(
|
71 |
+
status_code=403,
|
72 |
+
detail="Error ID: 118",
|
73 |
+
) # user has no authorization to create personal note for another user
|
74 |
+
else:
|
75 |
+
personal_note = crud_personal_note.create(db, obj_in=obj_in)
|
76 |
+
return personal_note
|
77 |
+
|
78 |
+
if current_user.user_type == settings.UserType.ADMIN.value:
|
79 |
+
raise HTTPException(
|
80 |
+
status_code=403,
|
81 |
+
detail="Error ID: 120",
|
82 |
+
) # user has no authorization to create personal notes
|
83 |
+
|
84 |
+
if current_user.user_type == settings.UserType.SUPERADMIN.value:
|
85 |
+
personal_note = crud_personal_note.create(db, obj_in=obj_in)
|
86 |
+
return personal_note
|
87 |
+
|
88 |
+
|
89 |
+
# get specific personal note,
|
90 |
+
# student and teacher can only get that specific note if they own it
|
91 |
+
# admin can has no permission
|
92 |
+
# superadmin can get it
|
93 |
+
@router.get("/{id}/", response_model=PersonalNote)
|
94 |
+
def get_specific_personal_note(
|
95 |
+
db: Session = Depends(deps.get_db),
|
96 |
+
*,
|
97 |
+
id: int,
|
98 |
+
current_user: User = Depends(deps.get_current_active_user),
|
99 |
+
) -> Any:
|
100 |
+
if not current_user:
|
101 |
+
# user not found!
|
102 |
+
raise HTTPException(status_code=404, detail="Error ID: 121")
|
103 |
+
|
104 |
+
if current_user.user_type == settings.UserType.ADMIN.value:
|
105 |
+
raise HTTPException(
|
106 |
+
status_code=403,
|
107 |
+
detail="Error ID: 122",
|
108 |
+
) # user has no authorization to get personal notes
|
109 |
+
|
110 |
+
if current_user.user_type >= settings.UserType.TEACHER.value:
|
111 |
+
personal_notes = get_personal_note(db, current_user=current_user)
|
112 |
+
for notes in personal_notes:
|
113 |
+
if id == notes.id:
|
114 |
+
personal_note = crud_personal_note.get(db, id)
|
115 |
+
return personal_note
|
116 |
+
|
117 |
+
raise HTTPException(
|
118 |
+
status_code=403,
|
119 |
+
detail="Error ID: 123",
|
120 |
+
) # user has no authorization to get other user's personal notes
|
121 |
+
|
122 |
+
if current_user.user_type == settings.UserType.SUPERADMIN.value:
|
123 |
+
personal_note = crud_personal_note.get(db, id)
|
124 |
+
return personal_note
|
125 |
+
|
126 |
+
|
127 |
+
@router.put("/{id}/", response_model=PersonalNote)
|
128 |
+
def update_personal_note(
|
129 |
+
db: Session = Depends(deps.get_db),
|
130 |
+
*,
|
131 |
+
id: int,
|
132 |
+
obj_in: PersonalNoteUpdate,
|
133 |
+
current_user: User = Depends(deps.get_current_active_user),
|
134 |
+
) -> Any:
|
135 |
+
if not current_user:
|
136 |
+
# user not found!
|
137 |
+
raise HTTPException(status_code=404, detail="Error ID: 124")
|
138 |
+
|
139 |
+
if current_user.user_type == settings.UserType.ADMIN.value:
|
140 |
+
raise HTTPException(
|
141 |
+
status_code=403,
|
142 |
+
detail="Error ID: 125",
|
143 |
+
) # user has no authorization to edit personal notes
|
144 |
+
|
145 |
+
if current_user.user_type >= settings.UserType.TEACHER.value:
|
146 |
+
if obj_in.user_id == current_user.id:
|
147 |
+
|
148 |
+
personal_note = crud_personal_note.get(db, id)
|
149 |
+
return crud_personal_note.update(db, db_obj=personal_note, obj_in=obj_in)
|
150 |
+
|
151 |
+
else:
|
152 |
+
raise HTTPException(
|
153 |
+
status_code=403,
|
154 |
+
detail="Error ID: 126",
|
155 |
+
) # user has no authorization to get other user's personal notes
|
156 |
+
|
157 |
+
if current_user.user_type == settings.UserType.SUPERADMIN.value:
|
158 |
+
personal_note = crud_personal_note.get(db, id)
|
159 |
+
return crud_personal_note.update(db, db_obj=personal_note, obj_in=obj_in)
|
160 |
+
|
161 |
+
|
162 |
+
# XXX: For deleting all, is this needed?
|
163 |
+
|
164 |
+
# @router.delete("/{}")
|
165 |
+
# def deletePersonalNotes(
|
166 |
+
# db: Session = Depends(deps.get_db),
|
167 |
+
# *,
|
168 |
+
# current_user: User = Depends(deps.get_current_active_superuser);
|
169 |
+
# )->Any:
|
170 |
+
# crud_personal_note.delete
|
171 |
+
|
172 |
+
|
173 |
+
@router.delete("/{id}/")
|
174 |
+
def deleteSpecificPersonalNote(
|
175 |
+
db: Session = Depends(deps.get_db),
|
176 |
+
*,
|
177 |
+
id: int,
|
178 |
+
current_user: User = Depends(deps.get_current_active_user),
|
179 |
+
) -> Any:
|
180 |
+
|
181 |
+
if current_user.user_type == settings.UserType.SUPERADMIN.value:
|
182 |
+
personalNote = crud_personal_note.remove(db, id=id)
|
183 |
+
return personalNote
|
184 |
+
|
185 |
+
if current_user.user_type == settings.UserType.ADMIN.value:
|
186 |
+
raise HTTPException(
|
187 |
+
status_code=403,
|
188 |
+
detail="Error ID: 142", # user has no authorization to delete notes of other users
|
189 |
+
)
|
190 |
+
|
191 |
+
personalNote = get_specific_personal_note(db, id=id, current_user=current_user)
|
192 |
+
|
193 |
+
personalNote = crud_personal_note.remove(db, id=personalNote.id)
|
194 |
+
|
195 |
+
return personalNote
|
api/endpoints/program.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends, HTTPException
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
from starlette.responses import Response
|
6 |
+
from core.cache import cache
|
7 |
+
|
8 |
+
from cruds.program import crud_program
|
9 |
+
from cruds.group import crud_group
|
10 |
+
from schemas import Program, ProgramCreate, ProgramUpdate, GroupCreate
|
11 |
+
from schemas import program
|
12 |
+
from utils import deps
|
13 |
+
|
14 |
+
router = APIRouter()
|
15 |
+
|
16 |
+
|
17 |
+
@router.get("/", response_model=List[Program])
|
18 |
+
async def get_programs(
|
19 |
+
db: Session = Depends(deps.get_db),
|
20 |
+
skip: int = 0,
|
21 |
+
limit: int = 500,
|
22 |
+
) -> Any:
|
23 |
+
programs = crud_program.get_multi(db, skip=skip, limit=limit)
|
24 |
+
return programs
|
25 |
+
|
26 |
+
|
27 |
+
@router.post("/", response_model=Program)
|
28 |
+
async def create_program(
|
29 |
+
db: Session = Depends(deps.get_db),
|
30 |
+
user=Depends(deps.get_current_admin_or_above),
|
31 |
+
*,
|
32 |
+
program_in: ProgramCreate,
|
33 |
+
) -> Any:
|
34 |
+
program = crud_program.create(db, obj_in=Program(**program_in.dict()))
|
35 |
+
for sem_iter in range(program_in.max_sems):
|
36 |
+
group = GroupCreate(
|
37 |
+
program_id=program.id,
|
38 |
+
sem=sem_iter + 1,
|
39 |
+
)
|
40 |
+
print(group.dict())
|
41 |
+
crud_group.create(db=db, obj_in=group)
|
42 |
+
return program
|
43 |
+
|
44 |
+
|
45 |
+
@router.get("/{program_id}/", response_model=Program)
|
46 |
+
@router.get("/{program_id}/group", response_model=program.ProgramGroupReturn)
|
47 |
+
async def get_program(
|
48 |
+
db: Session = Depends(deps.get_db),
|
49 |
+
user=Depends(deps.get_current_active_user),
|
50 |
+
*,
|
51 |
+
program_id: int,
|
52 |
+
) -> Any:
|
53 |
+
program = crud_program.get(db, program_id)
|
54 |
+
return program
|
55 |
+
|
56 |
+
|
57 |
+
@router.put("/{program_id}/")
|
58 |
+
def update_program(
|
59 |
+
db: Session = Depends(deps.get_db),
|
60 |
+
*,
|
61 |
+
program_id: int,
|
62 |
+
obj_in: ProgramUpdate,
|
63 |
+
current_user=Depends(deps.get_current_admin_or_above),
|
64 |
+
) -> Any:
|
65 |
+
department = crud_program.get(db, program_id)
|
66 |
+
crud_program.update(db, db_obj=department, obj_in=obj_in)
|
67 |
+
return {"status": "success"}
|
68 |
+
|
69 |
+
|
70 |
+
@router.delete("/{program_id}/")
|
71 |
+
async def delete_program(
|
72 |
+
db: Session = Depends(deps.get_db),
|
73 |
+
user=Depends(deps.get_current_admin_or_above),
|
74 |
+
*,
|
75 |
+
program_id: int,
|
76 |
+
):
|
77 |
+
crud_program.remove(db=db, id=program_id)
|
78 |
+
return {"msg": "success"}
|
api/endpoints/quiz.py
ADDED
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from typing import Any, List, Dict
|
3 |
+
|
4 |
+
from hashlib import sha1
|
5 |
+
|
6 |
+
import os
|
7 |
+
import shutil
|
8 |
+
|
9 |
+
from fastapi import APIRouter, Depends, HTTPException
|
10 |
+
from sqlalchemy.orm import Session
|
11 |
+
from core.config import settings
|
12 |
+
import json
|
13 |
+
from models import User
|
14 |
+
from utils import deps
|
15 |
+
from cruds import crud_quiz, crud_question
|
16 |
+
from schemas import (
|
17 |
+
Quiz,
|
18 |
+
QuizCreate,
|
19 |
+
QuizUpdate,
|
20 |
+
QuizAnswer,
|
21 |
+
QuizAnswerCreate,
|
22 |
+
QuizAnswerUpdate,
|
23 |
+
QuizQuestion,
|
24 |
+
QuizQuestionCreate,
|
25 |
+
QuizQuestionUpdate,
|
26 |
+
QuizQuestionwoutAnswer,
|
27 |
+
)
|
28 |
+
|
29 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
30 |
+
from fastapi.responses import FileResponse
|
31 |
+
import aiofiles
|
32 |
+
from core.config import settings
|
33 |
+
|
34 |
+
router = APIRouter()
|
35 |
+
|
36 |
+
QUIZ_ROUTE: str = "quiz"
|
37 |
+
QUIZ_QUESTION_UPLOAD_DIR: str = "question_image"
|
38 |
+
QUIZ_OPTION_UPLOAD_DIR: str = "option_image"
|
39 |
+
hashedQuestionRoute = sha1(
|
40 |
+
QUIZ_QUESTION_UPLOAD_DIR.encode(encoding="UTF-8", errors="strict")
|
41 |
+
)
|
42 |
+
hashedOptionRoute = sha1(
|
43 |
+
QUIZ_OPTION_UPLOAD_DIR.encode(encoding="UTF-8", errors="strict")
|
44 |
+
)
|
45 |
+
|
46 |
+
|
47 |
+
@router.get("/", response_model=List[Quiz])
|
48 |
+
async def get_quiz(
|
49 |
+
db: Session = Depends(deps.get_db),
|
50 |
+
skip: int = 0,
|
51 |
+
limit: int = -1,
|
52 |
+
current_user: User = Depends(deps.get_current_active_user),
|
53 |
+
) -> Any:
|
54 |
+
quiz = crud_quiz.get_multi(db, skip=skip, limit=limit)
|
55 |
+
|
56 |
+
if current_user.user_type == settings.UserType.STUDENT.value:
|
57 |
+
quiz_list = []
|
58 |
+
for quizItem in quiz:
|
59 |
+
for group in quizItem.group:
|
60 |
+
if group.id == current_user.group.id:
|
61 |
+
quiz_list.append(quizItem)
|
62 |
+
|
63 |
+
return quiz_list
|
64 |
+
|
65 |
+
if current_user.user_type == settings.UserType.TEACHER.value:
|
66 |
+
|
67 |
+
quiz_list = []
|
68 |
+
for quizItem in quiz:
|
69 |
+
for instructor in quizItem.instructor:
|
70 |
+
if current_user.id == instructor.id:
|
71 |
+
quiz_list.append(quizItem)
|
72 |
+
return quiz_list
|
73 |
+
|
74 |
+
if current_user.user_type <= settings.UserType.ADMIN.value:
|
75 |
+
return quiz
|
76 |
+
|
77 |
+
|
78 |
+
@router.post("/")
|
79 |
+
async def create_quiz(
|
80 |
+
db: Session = Depends(deps.get_db),
|
81 |
+
*,
|
82 |
+
obj_in: QuizCreate,
|
83 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
84 |
+
) -> Any:
|
85 |
+
quiz = crud_quiz.create(db, obj_in=obj_in)
|
86 |
+
return {"msg": "success", "id": quiz.id}
|
87 |
+
|
88 |
+
|
89 |
+
@router.get("/{id}", response_model=Quiz)
|
90 |
+
async def get_specific_quiz(
|
91 |
+
db: Session = Depends(deps.get_db),
|
92 |
+
*,
|
93 |
+
id: int,
|
94 |
+
current_user: User = Depends(deps.get_current_active_user),
|
95 |
+
) -> Any:
|
96 |
+
if current_user.user_type == settings.UserType.STUDENT.value:
|
97 |
+
quiz_list = await get_quiz(db=db, current_user=current_user)
|
98 |
+
for quiz in quiz_list:
|
99 |
+
if quiz.id == id:
|
100 |
+
return quiz
|
101 |
+
raise HTTPException(
|
102 |
+
status_code=403, detail="Error ID: 133"
|
103 |
+
) # not accessible by the Student user
|
104 |
+
|
105 |
+
if current_user.user_type == settings.UserType.TEACHER.value:
|
106 |
+
quiz_list = await get_quiz(db=db, current_user=current_user)
|
107 |
+
for quiz in quiz_list:
|
108 |
+
if quiz.id == id:
|
109 |
+
return quiz
|
110 |
+
raise HTTPException(
|
111 |
+
status_code=403, detail="Error ID: 134"
|
112 |
+
) # not accessible by the Teacher user
|
113 |
+
|
114 |
+
if current_user.user_type <= settings.UserType.ADMIN.value:
|
115 |
+
quiz = crud_quiz.get(db, id)
|
116 |
+
return quiz
|
117 |
+
|
118 |
+
|
119 |
+
@router.put("/{id}", response_model=QuizUpdate)
|
120 |
+
async def update_quiz(
|
121 |
+
db: Session = Depends(deps.get_db),
|
122 |
+
*,
|
123 |
+
id: int,
|
124 |
+
obj_in: QuizUpdate,
|
125 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
126 |
+
) -> Any:
|
127 |
+
quiz = crud_quiz.get(db, id)
|
128 |
+
quiz = crud_quiz.update(db, db_obj=quiz, obj_in=obj_in)
|
129 |
+
return quiz
|
130 |
+
|
131 |
+
|
132 |
+
@router.get("/{quizid}/question", response_model=List[QuizQuestionwoutAnswer])
|
133 |
+
async def get_question(
|
134 |
+
db: Session = Depends(deps.get_db),
|
135 |
+
*,
|
136 |
+
quizid: int,
|
137 |
+
current_user: User = Depends(deps.get_current_active_user),
|
138 |
+
) -> Any:
|
139 |
+
quiz = await get_specific_quiz(db, id=quizid, current_user=current_user)
|
140 |
+
if not quiz:
|
141 |
+
raise HTTPException(
|
142 |
+
status_code=404, detail="Error ID = 135"
|
143 |
+
) # quiz not found in database
|
144 |
+
|
145 |
+
questions = crud_question.get_all_by_quiz_id(db, quiz_id=quiz.id)
|
146 |
+
return questions
|
147 |
+
|
148 |
+
|
149 |
+
@router.get("/{quizid}/question/{id}", response_model=QuizQuestionwoutAnswer)
|
150 |
+
async def get_specific_question(
|
151 |
+
db: Session = Depends(deps.get_db),
|
152 |
+
*,
|
153 |
+
quizid: int,
|
154 |
+
id: int,
|
155 |
+
current_user: User = Depends(deps.get_current_active_user),
|
156 |
+
) -> Any:
|
157 |
+
|
158 |
+
question = crud_question.get_by_quiz_id_question_id(
|
159 |
+
db=db, quiz_id=quizid, questionid=id
|
160 |
+
)
|
161 |
+
|
162 |
+
if not question:
|
163 |
+
raise HTTPException(
|
164 |
+
status_code=404, detail="Error ID: 136"
|
165 |
+
) # question specific to that id not found
|
166 |
+
|
167 |
+
return question
|
168 |
+
|
169 |
+
|
170 |
+
@router.post("/{quizid}/question")
|
171 |
+
async def create_question(
|
172 |
+
db: Session = Depends(deps.get_db),
|
173 |
+
*,
|
174 |
+
quizid: int,
|
175 |
+
obj_in: QuizQuestionCreate,
|
176 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
177 |
+
) -> Any:
|
178 |
+
obj_in.quiz_id = quizid
|
179 |
+
# obj_in.question_image = "question"
|
180 |
+
# obj_in.options = [
|
181 |
+
# {"image": eachDict["image"] if eachDict["image"] == "" else f"Options{index+1}"}
|
182 |
+
# for index, eachDict in enumerate(obj_in.options)
|
183 |
+
# ]
|
184 |
+
|
185 |
+
question = crud_question.create(db, obj_in=obj_in)
|
186 |
+
quiz = crud_quiz.get(db=db, id=quizid)
|
187 |
+
|
188 |
+
newMarks = quiz.total_marks + question.marks
|
189 |
+
newQuiz = QuizUpdate(total_marks=newMarks)
|
190 |
+
|
191 |
+
crud_quiz.update(db=db, db_obj=quiz, obj_in=newQuiz)
|
192 |
+
|
193 |
+
hashedQuizId = sha1(str(quizid).encode(encoding="UTF-8", errors="strict"))
|
194 |
+
|
195 |
+
hashedQuestionId = sha1(str(question.id).encode(encoding="UTF-8", errors="strict"))
|
196 |
+
|
197 |
+
# if the question is said to have a IMAGE then only create the folder to store the image
|
198 |
+
FILE_PATH = os.path.join(
|
199 |
+
settings.UPLOAD_DIR_ROOT,
|
200 |
+
QUIZ_ROUTE,
|
201 |
+
hashedQuizId.hexdigest(),
|
202 |
+
hashedQuestionId.hexdigest(),
|
203 |
+
)
|
204 |
+
|
205 |
+
FILE_PATH_QUESTION = os.path.join(FILE_PATH, hashedQuestionRoute.hexdigest())
|
206 |
+
|
207 |
+
FILE_PATH_OPTION = os.path.join(FILE_PATH, hashedOptionRoute.hexdigest())
|
208 |
+
|
209 |
+
if not os.path.exists(FILE_PATH_QUESTION):
|
210 |
+
os.makedirs(FILE_PATH_QUESTION)
|
211 |
+
|
212 |
+
if not os.path.exists(FILE_PATH_OPTION):
|
213 |
+
os.makedirs(FILE_PATH_OPTION)
|
214 |
+
|
215 |
+
return {"msg": "success", "id": question.id}
|
216 |
+
|
217 |
+
|
218 |
+
@router.put("/{quizid}/question/{id}")
|
219 |
+
async def update_question(
|
220 |
+
db: Session = Depends(deps.get_db),
|
221 |
+
*,
|
222 |
+
quizid: int,
|
223 |
+
obj_in: QuizQuestionUpdate,
|
224 |
+
id: int,
|
225 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
226 |
+
) -> Any:
|
227 |
+
|
228 |
+
question = crud_question.get(db, id)
|
229 |
+
|
230 |
+
# on question_type update, create folder to store image if not already present
|
231 |
+
FILE_PATH_QUESTION = os.path.join(
|
232 |
+
settings.UPLOAD_DIR_ROOT,
|
233 |
+
QUIZ_QUESTION_UPLOAD_DIR,
|
234 |
+
f"{quizid}/{question.id}",
|
235 |
+
)
|
236 |
+
|
237 |
+
if not os.path.exists(FILE_PATH_QUESTION):
|
238 |
+
os.makedirs(FILE_PATH_QUESTION)
|
239 |
+
|
240 |
+
# on option_type update, create folder to store image if not already present
|
241 |
+
# if (obj_in.answer_type == AnswerType.IMAGE_OPTIONS.value) and (
|
242 |
+
# question.answer_type != obj_in.answer_type
|
243 |
+
# ):
|
244 |
+
# FILE_PATH_OPTION = os.path.join(
|
245 |
+
# "static", QUIZ_OPTION_UPLOAD_DIR, f"{quizid}/{question.id}"
|
246 |
+
# )
|
247 |
+
# FILE_PATH_OPTION = os.path.join(current_directory, FILE_PATH_OPTION)
|
248 |
+
|
249 |
+
# if not os.path.exists(FILE_PATH_OPTION):
|
250 |
+
# os.makedirs(FILE_PATH_OPTION)
|
251 |
+
|
252 |
+
if question.quiz_id == quizid == obj_in.quiz_id:
|
253 |
+
question = crud_question.update(db, db_obj=question, obj_in=obj_in)
|
254 |
+
return question
|
255 |
+
else:
|
256 |
+
raise HTTPException(
|
257 |
+
status_code=403, detail="Error ID = 137"
|
258 |
+
) # noqa Access Denied!
|
259 |
+
|
260 |
+
|
261 |
+
# XXX: ENDPOINTS for questions to write and read files and answers to those files
|
262 |
+
|
263 |
+
|
264 |
+
# FIXME: Uploaded files directory fix it
|
265 |
+
@router.post("/{quizid}/question/{id}/question_image/")
|
266 |
+
async def create_question_files(
|
267 |
+
db: Session = Depends(deps.get_db),
|
268 |
+
files: List[UploadFile] = File(...),
|
269 |
+
current_user=Depends(deps.get_current_active_teacher_or_above),
|
270 |
+
*,
|
271 |
+
quizid: int,
|
272 |
+
id: int,
|
273 |
+
):
|
274 |
+
question = await get_specific_question(
|
275 |
+
db, quizid=quizid, id=id, current_user=current_user
|
276 |
+
)
|
277 |
+
|
278 |
+
hashedQuizId = sha1(str(quizid).encode(encoding="UTF-8", errors="strict"))
|
279 |
+
|
280 |
+
hashedQuestionId = sha1(str(id).encode(encoding="UTF-8", errors="strict"))
|
281 |
+
|
282 |
+
FILE_QUESTION_PATH = os.path.join(
|
283 |
+
QUIZ_ROUTE,
|
284 |
+
hashedQuizId.hexdigest(),
|
285 |
+
hashedQuestionId.hexdigest(),
|
286 |
+
hashedQuestionRoute.hexdigest(),
|
287 |
+
)
|
288 |
+
|
289 |
+
FILE_PATH = os.path.join(
|
290 |
+
settings.UPLOAD_DIR_ROOT,
|
291 |
+
FILE_QUESTION_PATH,
|
292 |
+
)
|
293 |
+
|
294 |
+
questionImages = []
|
295 |
+
fileIndex = 0
|
296 |
+
for file in files:
|
297 |
+
fileName, fileExtension = os.path.splitext(file.filename)
|
298 |
+
hashedFileName = sha1(
|
299 |
+
(fileName + str(fileIndex)).encode(encoding="UTF-8", errors="strict")
|
300 |
+
)
|
301 |
+
fileIndex = fileIndex + 1
|
302 |
+
filename = f"{FILE_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
303 |
+
async with aiofiles.open(filename, mode="wb") as f:
|
304 |
+
content = await file.read()
|
305 |
+
await f.write(content)
|
306 |
+
questionImages.append(
|
307 |
+
f"{FILE_QUESTION_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
308 |
+
)
|
309 |
+
|
310 |
+
obj_in = QuizQuestionUpdate(quiz_id=quizid, question_image=questionImages)
|
311 |
+
updated = crud_question.update(db=db, db_obj=question, obj_in=obj_in)
|
312 |
+
|
313 |
+
return updated
|
314 |
+
|
315 |
+
|
316 |
+
@router.post("/{quizid}/question/{id}/option_image/")
|
317 |
+
async def create_option_files(
|
318 |
+
options: List,
|
319 |
+
db: Session = Depends(deps.get_db),
|
320 |
+
files: List[UploadFile] = File(...),
|
321 |
+
current_user=Depends(deps.get_current_active_teacher_or_above),
|
322 |
+
*,
|
323 |
+
quizid: int,
|
324 |
+
id: int,
|
325 |
+
):
|
326 |
+
options = json.loads(options[0])
|
327 |
+
|
328 |
+
question = await get_specific_question(
|
329 |
+
db, quizid=quizid, id=id, current_user=current_user
|
330 |
+
)
|
331 |
+
|
332 |
+
hashedQuizId = sha1(str(quizid).encode(encoding="UTF-8", errors="strict"))
|
333 |
+
|
334 |
+
hashedQuestionId = sha1(str(id).encode(encoding="UTF-8", errors="strict"))
|
335 |
+
FILE_OPTION_PATH = os.path.join(
|
336 |
+
QUIZ_ROUTE,
|
337 |
+
hashedQuizId.hexdigest(),
|
338 |
+
hashedQuestionId.hexdigest(),
|
339 |
+
hashedOptionRoute.hexdigest(),
|
340 |
+
)
|
341 |
+
FILE_PATH = os.path.join(
|
342 |
+
settings.UPLOAD_DIR_ROOT,
|
343 |
+
FILE_OPTION_PATH,
|
344 |
+
)
|
345 |
+
fileIndex = 0
|
346 |
+
for file in files:
|
347 |
+
fileName, fileExtension = os.path.splitext(file.filename)
|
348 |
+
hashedFileName = sha1(
|
349 |
+
(fileName + str(fileIndex)).encode(encoding="UTF-8", errors="strict")
|
350 |
+
)
|
351 |
+
fileIndex = fileIndex + 1
|
352 |
+
filename = f"{FILE_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
353 |
+
async with aiofiles.open(filename, mode="wb") as f:
|
354 |
+
content = await file.read()
|
355 |
+
await f.write(content)
|
356 |
+
alreadyModified = []
|
357 |
+
for index, eachDict in enumerate(options):
|
358 |
+
if eachDict["image"] == file.filename:
|
359 |
+
if index not in alreadyModified:
|
360 |
+
eachDict[
|
361 |
+
"image"
|
362 |
+
] = f"{FILE_OPTION_PATH}/{hashedFileName.hexdigest()}{fileExtension}"
|
363 |
+
alreadyModified.append(index)
|
364 |
+
break
|
365 |
+
|
366 |
+
obj_in = QuizQuestionUpdate(quiz_id=quizid, options=json.dumps(options))
|
367 |
+
updated = crud_question.update(db=db, db_obj=question, obj_in=obj_in)
|
368 |
+
|
369 |
+
return {"msg": "success"}
|
370 |
+
|
371 |
+
|
372 |
+
@router.get("/{quizid}/question/{id}/{type}/{filename}")
|
373 |
+
async def get_image(
|
374 |
+
db: Session = Depends(deps.get_db),
|
375 |
+
*,
|
376 |
+
quizid: int,
|
377 |
+
id: int,
|
378 |
+
filename: str,
|
379 |
+
type: int,
|
380 |
+
current_user: User = Depends(deps.get_current_active_user),
|
381 |
+
):
|
382 |
+
question = await get_specific_question(
|
383 |
+
db, quizid=quizid, id=id, current_user=current_user
|
384 |
+
)
|
385 |
+
|
386 |
+
if not question:
|
387 |
+
raise HTTPException(status_code=404, detail="Error ID: 138")
|
388 |
+
# question not found error
|
389 |
+
|
390 |
+
if type == 1:
|
391 |
+
if filename in question.question_image:
|
392 |
+
FILE_PATH = os.path.join(
|
393 |
+
settings.UPLOAD_DIR_ROOT, QUIZ_QUESTION_UPLOAD_DIR, f"{quizid}/{id}"
|
394 |
+
)
|
395 |
+
else:
|
396 |
+
raise HTTPException(
|
397 |
+
status_code=403, detail="Error ID: 139"
|
398 |
+
) # file not of that question
|
399 |
+
|
400 |
+
if type == 2:
|
401 |
+
if filename in question.option_image:
|
402 |
+
FILE_PATH = os.path.join(
|
403 |
+
settings.UPLOAD_DIR_ROOT, QUIZ_OPTION_UPLOAD_DIR, f"{quizid}/{id}"
|
404 |
+
)
|
405 |
+
else:
|
406 |
+
raise HTTPException(
|
407 |
+
status_code=403, detail="Error ID: 140"
|
408 |
+
) # file not of that question
|
409 |
+
|
410 |
+
FILE_PATH = os.path.join(FILE_PATH, filename)
|
411 |
+
|
412 |
+
if os.path.isfile(FILE_PATH):
|
413 |
+
file = FileResponse(f"{FILE_PATH}")
|
414 |
+
return file
|
415 |
+
else:
|
416 |
+
raise HTTPException(
|
417 |
+
status_code=404, detail="Error ID: 141"
|
418 |
+
) # no file exist in the path
|
419 |
+
|
420 |
+
|
421 |
+
@router.delete("/{quizid}/")
|
422 |
+
async def delete_quiz(
|
423 |
+
db: Session = Depends(deps.get_db),
|
424 |
+
*,
|
425 |
+
quizid=int,
|
426 |
+
current_user: User = Depends(deps.get_current_active_teacher),
|
427 |
+
):
|
428 |
+
quiz = crud_quiz.get(db, id=quizid)
|
429 |
+
|
430 |
+
if not quiz:
|
431 |
+
raise HTTPException(status_code=404, detail="Error ID: 143")
|
432 |
+
|
433 |
+
for instructor in quiz.instructor:
|
434 |
+
|
435 |
+
if instructor.id == current_user.id:
|
436 |
+
|
437 |
+
print(instructor.id, current_user.id)
|
438 |
+
quiz = crud_quiz.remove(db, id=quizid)
|
439 |
+
|
440 |
+
hashedQuizId = sha1(str(quizid).encode(encoding="UTF-8", errors="strict"))
|
441 |
+
|
442 |
+
FILE_PATH = os.path.join(
|
443 |
+
settings.UPLOAD_DIR_ROOT,
|
444 |
+
QUIZ_ROUTE,
|
445 |
+
hashedQuizId.hexdigest(),
|
446 |
+
)
|
447 |
+
|
448 |
+
if os.path.exists(FILE_PATH):
|
449 |
+
shutil.rmtree(FILE_PATH)
|
450 |
+
|
451 |
+
return {"msg": "delete success"}
|
452 |
+
|
453 |
+
raise HTTPException(
|
454 |
+
status_code=403,
|
455 |
+
detail="Error ID: 142",
|
456 |
+
) # teacher not associated with the quiz
|
api/endpoints/quiz_answer.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
3 |
+
from sqlalchemy.orm import Session
|
4 |
+
from models import User
|
5 |
+
from utils import deps
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
from cruds import crud_quiz_answer, crud_question, crud_quiz
|
8 |
+
from schemas import (
|
9 |
+
QuizAnswer,
|
10 |
+
QuizAnswerCreate,
|
11 |
+
QuizAnswerUpdate,
|
12 |
+
QuizAnsweronlySelected,
|
13 |
+
QuizAnswerwithName,
|
14 |
+
)
|
15 |
+
|
16 |
+
from typing import Any, Optional, List, Dict # noqa
|
17 |
+
|
18 |
+
|
19 |
+
router = APIRouter()
|
20 |
+
|
21 |
+
|
22 |
+
@router.get("/")
|
23 |
+
async def get_answers(
|
24 |
+
db: Session = Depends(deps.get_db),
|
25 |
+
*,
|
26 |
+
current_user: User = Depends(deps.get_current_active_user),
|
27 |
+
):
|
28 |
+
pass
|
29 |
+
|
30 |
+
|
31 |
+
@router.get("/{quizid}", response_model=QuizAnswer, response_model_exclude_none=True)
|
32 |
+
async def get_answers_quiz(
|
33 |
+
db: Session = Depends(deps.get_db),
|
34 |
+
*,
|
35 |
+
quizid: int,
|
36 |
+
current_user: User = Depends(deps.get_current_active_user),
|
37 |
+
):
|
38 |
+
answer = crud_quiz_answer.get_by_quiz_id(
|
39 |
+
db=db, quizId=quizid, studentId=current_user.id
|
40 |
+
)
|
41 |
+
|
42 |
+
if answer:
|
43 |
+
marks = answer.marks_obtained
|
44 |
+
answer.marks_obtained = None
|
45 |
+
|
46 |
+
quiz = crud_quiz.get(db=db, id=quizid)
|
47 |
+
|
48 |
+
if (
|
49 |
+
quiz
|
50 |
+
and quiz.end_time
|
51 |
+
and quiz.end_time <= (datetime.utcnow() - timedelta(seconds=15))
|
52 |
+
):
|
53 |
+
answer.marks_obtained = marks
|
54 |
+
|
55 |
+
return answer
|
56 |
+
|
57 |
+
raise HTTPException(status_code=404, detail="Error ID: 144")
|
58 |
+
|
59 |
+
|
60 |
+
@router.get("/{quizid}/getAnswersAsTeacher/", response_model=List[QuizAnswerwithName])
|
61 |
+
async def get_quiz_answers_as_teacher(
|
62 |
+
db: Session = Depends(deps.get_db),
|
63 |
+
*,
|
64 |
+
quizid: int,
|
65 |
+
current_user: User = Depends(deps.get_current_active_teacher_or_above),
|
66 |
+
):
|
67 |
+
if current_user.quiz:
|
68 |
+
for quiz in current_user.quiz:
|
69 |
+
if quiz.id == quizid:
|
70 |
+
answers = crud_quiz_answer.get_all_by_quiz_id_as_teacher(
|
71 |
+
db=db, quizId=quizid
|
72 |
+
)
|
73 |
+
if len(answers) >= 1:
|
74 |
+
return answers
|
75 |
+
|
76 |
+
raise HTTPException(
|
77 |
+
status_code=404,
|
78 |
+
detail="Error ID: 143", # could not populate answer
|
79 |
+
)
|
80 |
+
|
81 |
+
|
82 |
+
@router.get("/{quizid}/exists/")
|
83 |
+
async def check_existence(
|
84 |
+
db: Session = Depends(deps.get_db),
|
85 |
+
*,
|
86 |
+
quizid: int,
|
87 |
+
current_user: User = Depends(deps.get_current_active_user),
|
88 |
+
):
|
89 |
+
answer = crud_quiz_answer.get_by_quiz_id(
|
90 |
+
db=db, quizId=quizid, studentId=current_user.id
|
91 |
+
)
|
92 |
+
if not answer:
|
93 |
+
return {"exists": False}
|
94 |
+
else:
|
95 |
+
return {"exists": True}
|
96 |
+
|
97 |
+
|
98 |
+
@router.get("/{id}")
|
99 |
+
async def get_specific_answer():
|
100 |
+
pass
|
101 |
+
|
102 |
+
|
103 |
+
@router.post("/{quiz_id}", response_model=QuizAnsweronlySelected)
|
104 |
+
async def submit_answer(
|
105 |
+
db: Session = Depends(deps.get_db),
|
106 |
+
*,
|
107 |
+
questionAnswer: Dict[int, Any],
|
108 |
+
quiz_id: int,
|
109 |
+
current_user: User = Depends(deps.get_current_active_user),
|
110 |
+
):
|
111 |
+
questions = crud_question.get_all_by_quiz_id(db, quiz_id=quiz_id)
|
112 |
+
|
113 |
+
marksObtained = 0
|
114 |
+
correctCount = 0
|
115 |
+
for question in questions:
|
116 |
+
if question.id in questionAnswer.keys():
|
117 |
+
questionOption = questionAnswer[question.id]
|
118 |
+
if question.multiple:
|
119 |
+
if len(question.answer) >= len(questionOption):
|
120 |
+
for answer in questionOption:
|
121 |
+
if answer in question.answer:
|
122 |
+
correctCount = correctCount + 1
|
123 |
+
|
124 |
+
correctCount = correctCount / len(question.answer)
|
125 |
+
|
126 |
+
else:
|
127 |
+
questionAnswer[question.id] = questionOption
|
128 |
+
|
129 |
+
if questionOption == question.answer[0]:
|
130 |
+
correctCount = 1
|
131 |
+
|
132 |
+
marksObtained = marksObtained + correctCount * question.marks
|
133 |
+
correctCount = 0
|
134 |
+
|
135 |
+
questionAnswer = QuizAnswerCreate(
|
136 |
+
marks_obtained=math.ceil(marksObtained),
|
137 |
+
options_selected=questionAnswer,
|
138 |
+
quiz_id=quiz_id,
|
139 |
+
student_id=current_user.id,
|
140 |
+
)
|
141 |
+
|
142 |
+
try:
|
143 |
+
questionAnswer = crud_quiz_answer.create(db, obj_in=questionAnswer)
|
144 |
+
return questionAnswer
|
145 |
+
except Exception:
|
146 |
+
raise HTTPException(
|
147 |
+
status_code=400,
|
148 |
+
detail="Error ID: 142", # could not populate answer
|
149 |
+
)
|
api/endpoints/school.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
|
6 |
+
from cruds.school import crud_school
|
7 |
+
from schemas import School, SchoolCreate, SchoolUpdate
|
8 |
+
from utils import deps
|
9 |
+
|
10 |
+
router = APIRouter()
|
11 |
+
|
12 |
+
|
13 |
+
@router.get("/", response_model=List[School])
|
14 |
+
async def get_schools(
|
15 |
+
db: Session = Depends(deps.get_db),
|
16 |
+
user=Depends(deps.get_current_active_user),
|
17 |
+
skip: int = 0,
|
18 |
+
limit: int = 100,
|
19 |
+
):
|
20 |
+
schools = crud_school.get_multi(db, skip=skip, limit=limit)
|
21 |
+
return schools
|
22 |
+
|
23 |
+
|
24 |
+
@router.post("/", response_model=School)
|
25 |
+
async def create_school(
|
26 |
+
db: Session = Depends(deps.get_db),
|
27 |
+
user=Depends(deps.get_current_admin_or_above),
|
28 |
+
*,
|
29 |
+
school_in: SchoolCreate,
|
30 |
+
):
|
31 |
+
school = crud_school.create(db, obj_in=school_in)
|
32 |
+
return school
|
33 |
+
|
34 |
+
|
35 |
+
@router.get("/{school_id}/", response_model=School)
|
36 |
+
async def get_school(
|
37 |
+
db: Session = Depends(deps.get_db),
|
38 |
+
user=Depends(deps.get_current_active_user),
|
39 |
+
*,
|
40 |
+
school_id: int,
|
41 |
+
):
|
42 |
+
school = crud_school.get(db, school_id)
|
43 |
+
return school
|
44 |
+
|
45 |
+
|
46 |
+
@router.put("/{school_id}/", response_model=School)
|
47 |
+
async def update_school(
|
48 |
+
db: Session = Depends(deps.get_db),
|
49 |
+
user=Depends(deps.get_current_admin_or_above),
|
50 |
+
*,
|
51 |
+
school_id: int,
|
52 |
+
school_update: SchoolUpdate,
|
53 |
+
):
|
54 |
+
school = crud_school.get(db, school_id)
|
55 |
+
school = crud_school.update(db, db_obj=school, obj_in=school_update)
|
56 |
+
return school
|
57 |
+
|
58 |
+
|
59 |
+
@router.delete("/{school_id}/")
|
60 |
+
async def delete_school(
|
61 |
+
db: Session = Depends(deps.get_db),
|
62 |
+
user=Depends(deps.get_current_admin_or_above),
|
63 |
+
*,
|
64 |
+
school_id: int,
|
65 |
+
):
|
66 |
+
crud_school.remove(db=db, id=school_id)
|
67 |
+
return {"msg": "success"}
|
api/endpoints/teacher_note.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends, HTTPException
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
|
6 |
+
from utils import deps
|
7 |
+
from cruds import crud_teacher_note
|
8 |
+
from schemas import TeacherNote, TeacherNoteUpdate, TeacherNoteCreate
|
9 |
+
|
10 |
+
|
11 |
+
router = APIRouter()
|
12 |
+
|
13 |
+
|
14 |
+
# TODO: Search by student ??
|
15 |
+
@router.get("/teacher_note/", response_model=List[TeacherNote])
|
16 |
+
async def get_teacher_note(
|
17 |
+
user=Depends(deps.get_current_active_teacher),
|
18 |
+
db: Session = Depends(deps.get_db),
|
19 |
+
skip: int = 0,
|
20 |
+
limit: int = 100,
|
21 |
+
) -> Any:
|
22 |
+
teacher_note = crud_teacher_note.get_user_teacher_note(db, user=user)
|
23 |
+
return teacher_note
|
24 |
+
|
25 |
+
|
26 |
+
# TODO: Teacher can only post notes on students that are his student
|
27 |
+
@router.post("/teacher_note/", response_model=TeacherNote)
|
28 |
+
async def create_teacher_note(
|
29 |
+
user=Depends(deps.get_current_active_teacher),
|
30 |
+
db: Session = Depends(deps.get_db),
|
31 |
+
*,
|
32 |
+
obj_in: TeacherNoteCreate
|
33 |
+
) -> Any:
|
34 |
+
teacher_note = crud_teacher_note.create(db, obj_in=obj_in)
|
35 |
+
return teacher_note
|
36 |
+
|
37 |
+
|
38 |
+
@router.get("/teacher_note/{id}/", response_model=TeacherNote)
|
39 |
+
async def get_specific_teacher_note(
|
40 |
+
user=Depends(deps.get_current_active_teacher),
|
41 |
+
db: Session = Depends(deps.get_db),
|
42 |
+
*,
|
43 |
+
id: int
|
44 |
+
) -> Any:
|
45 |
+
teacher_note = crud_teacher_note.get_user_teacher_note(db=db, user=user, id=id)
|
46 |
+
return teacher_note
|
47 |
+
|
48 |
+
|
49 |
+
@router.put("/teacher_note/{id}/", response_model=TeacherNote)
|
50 |
+
async def update_teacher_note(
|
51 |
+
user=Depends(deps.get_current_active_teacher),
|
52 |
+
db: Session = Depends(deps.get_db),
|
53 |
+
*,
|
54 |
+
id: int,
|
55 |
+
obj_in: TeacherNoteUpdate
|
56 |
+
) -> Any:
|
57 |
+
teacher_note = crud_teacher_note.get_user_teacher_note(db=db, user=user, id=id)
|
58 |
+
if not teacher_note:
|
59 |
+
raise HTTPException(status_code=403, detail="Error ID: 127") # Access denied!
|
60 |
+
teacher_note = crud_teacher_note.update(db, db_obj=teacher_note, obj_in=obj_in)
|
61 |
+
return teacher_note
|
api/endpoints/two_fa.py
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from typing import Any, List, Optional
|
4 |
+
|
5 |
+
import aiofiles
|
6 |
+
from fastapi import APIRouter, Body
|
7 |
+
from fastapi import Cookie as ReqCookie
|
8 |
+
from fastapi import Depends, File, HTTPException, Request, UploadFile
|
9 |
+
from fastapi.params import Cookie
|
10 |
+
from sqlalchemy.orm import Session
|
11 |
+
from sqlalchemy.sql.expression import update
|
12 |
+
from sqlalchemy.sql.functions import current_user
|
13 |
+
from starlette.responses import JSONResponse, Response
|
14 |
+
|
15 |
+
import cruds
|
16 |
+
import models
|
17 |
+
import schemas
|
18 |
+
from core import throttle
|
19 |
+
from core.config import settings
|
20 |
+
from core.db import redis_session_client
|
21 |
+
from core.security import (
|
22 |
+
create_sesssion_token,
|
23 |
+
get_password_hash,
|
24 |
+
create_2fa_temp_token,
|
25 |
+
create_2fa_enable_temp_token
|
26 |
+
)
|
27 |
+
from cruds import group
|
28 |
+
from schemas.user import UserUpdate, VerifyUser
|
29 |
+
from utils import deps
|
30 |
+
from utils.utils import (
|
31 |
+
expire_web_session,
|
32 |
+
generate_password_reset_token,
|
33 |
+
send_reset_password_email,
|
34 |
+
send_verification_email,
|
35 |
+
verify_password_reset_token,
|
36 |
+
verify_user_verify_token,
|
37 |
+
)
|
38 |
+
import pyotp
|
39 |
+
from cruds import crud_user
|
40 |
+
|
41 |
+
|
42 |
+
router = APIRouter()
|
43 |
+
|
44 |
+
|
45 |
+
@router.get("/enable/request")
|
46 |
+
async def two_fa_enable_request(
|
47 |
+
db: Session = Depends(deps.get_db),
|
48 |
+
*,
|
49 |
+
current_user: models.User = Depends(deps.get_current_user),
|
50 |
+
request: Request,
|
51 |
+
response: Response,
|
52 |
+
) -> Any:
|
53 |
+
if current_user.two_fa_secret != None:
|
54 |
+
raise HTTPException(
|
55 |
+
status_code=409, detail="2FA is already enabled!"
|
56 |
+
)
|
57 |
+
|
58 |
+
totp_secret = pyotp.random_base32()
|
59 |
+
await create_2fa_enable_temp_token(current_user, totp_secret)
|
60 |
+
|
61 |
+
totp_url = pyotp.totp.TOTP(totp_secret).provisioning_uri(
|
62 |
+
name=current_user.email,
|
63 |
+
issuer_name=settings.PROJECT_NAME
|
64 |
+
)
|
65 |
+
|
66 |
+
return {"msg": "2FA enable requested!", "uri": totp_url, "secret": totp_secret}
|
67 |
+
|
68 |
+
|
69 |
+
@router.post("/enable/confirm")
|
70 |
+
async def two_fa_enable_confirm(
|
71 |
+
db: Session = Depends(deps.get_db),
|
72 |
+
*,
|
73 |
+
form_data: schemas.Two_FA_Confirm,
|
74 |
+
current_user: models.User = Depends(deps.get_current_user),
|
75 |
+
) -> Any:
|
76 |
+
totp_secret = await redis_session_client.client.get(
|
77 |
+
f"two_fa_enable_temp_{current_user.id}"
|
78 |
+
)
|
79 |
+
totp_secret = totp_secret.decode("utf-8")
|
80 |
+
if not totp_secret:
|
81 |
+
raise HTTPException(
|
82 |
+
status_code=403, detail="Invalid or expired TOTP"
|
83 |
+
)
|
84 |
+
|
85 |
+
totp = pyotp.TOTP(totp_secret)
|
86 |
+
totp_valid = totp.verify(str(form_data.totp), valid_window=1)
|
87 |
+
|
88 |
+
if totp_valid:
|
89 |
+
crud_user.enable_2fa(db, secret=totp_secret, db_obj=current_user)
|
90 |
+
await redis_session_client.client.delete(
|
91 |
+
f"two_fa_enable_temp_{current_user.id}"
|
92 |
+
)
|
93 |
+
return {"msg": "2FA successfully enabled!"}
|
94 |
+
else:
|
95 |
+
return {"msg": "Invalid TOTP!"}
|
96 |
+
|
97 |
+
|
98 |
+
@router.post("/login/confirm", response_model=Optional[schemas.user.UserLoginReturn], response_model_exclude_none=True)
|
99 |
+
async def two_fa_login_confirm(
|
100 |
+
db: Session = Depends(deps.get_db),
|
101 |
+
*,
|
102 |
+
form_data: schemas.Two_FA_Confirm,
|
103 |
+
request: Request,
|
104 |
+
response: Response
|
105 |
+
) -> Any:
|
106 |
+
token = request.cookies.get("temp_session")
|
107 |
+
|
108 |
+
if token == None:
|
109 |
+
raise HTTPException(
|
110 |
+
status_code=403, detail="Invalid token!"
|
111 |
+
)
|
112 |
+
|
113 |
+
data = json.loads(await redis_session_client.client.get(
|
114 |
+
f"two_fa_temp_{token}",
|
115 |
+
))
|
116 |
+
# json.dumps({"user": user.id, "remember_me": remember_me}),
|
117 |
+
|
118 |
+
user = crud_user.get(db, id=data.get("user"))
|
119 |
+
|
120 |
+
totp = pyotp.TOTP(user.two_fa_secret)
|
121 |
+
totp_valid = totp.verify(str(form_data.totp), valid_window=1)
|
122 |
+
|
123 |
+
if not totp_valid:
|
124 |
+
raise HTTPException(
|
125 |
+
status_code=403, detail="Invalid TOTP!"
|
126 |
+
)
|
127 |
+
|
128 |
+
session_token = await create_sesssion_token(user, data.get("remember_me"), request)
|
129 |
+
|
130 |
+
response.delete_cookie("temp_session")
|
131 |
+
response.set_cookie("session", session_token, httponly=True)
|
132 |
+
|
133 |
+
await redis_session_client.client.delete(f"two_fa_temp_{token}")
|
134 |
+
return {"msg": "Logged in successfully!", "user": user, "two_fa_required": None}
|
135 |
+
|
136 |
+
|
137 |
+
@router.delete(
|
138 |
+
"/disable",
|
139 |
+
)
|
140 |
+
async def two_fa_disable(
|
141 |
+
db: Session = Depends(deps.get_db),
|
142 |
+
*,
|
143 |
+
current_user: models.User = Depends(deps.get_current_user),
|
144 |
+
) -> Any:
|
145 |
+
if current_user.two_fa_secret == None:
|
146 |
+
raise HTTPException(
|
147 |
+
status_code=409, detail="2FA is already disabled!"
|
148 |
+
)
|
149 |
+
crud_user.disable_2fa(db, db_obj=current_user)
|
150 |
+
|
151 |
+
return {"msg": "2FA successfully disabled!"}
|
api/endpoints/user_permission.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends
|
4 |
+
from sqlalchemy.orm import Session
|
5 |
+
|
6 |
+
from utils import deps
|
7 |
+
from cruds import crud_user_permission
|
8 |
+
from schemas.user_permission import (
|
9 |
+
UserPermission,
|
10 |
+
UserPermissionCreate,
|
11 |
+
UserPermissionUpdate,
|
12 |
+
)
|
13 |
+
|
14 |
+
router = APIRouter()
|
15 |
+
|
16 |
+
|
17 |
+
@router.get("/", response_model=List[UserPermission])
|
18 |
+
async def get_user_permission(
|
19 |
+
db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100
|
20 |
+
) -> Any:
|
21 |
+
user_permission = crud_user_permission.get_multi(db, skip=skip, limit=limit)
|
22 |
+
return user_permission
|
23 |
+
|
24 |
+
|
25 |
+
@router.post("/", response_model=UserPermission)
|
26 |
+
async def create_user_permission(
|
27 |
+
db: Session = Depends(deps.get_db), *, obj_in: UserPermissionCreate
|
28 |
+
) -> Any:
|
29 |
+
user_permission = crud_user_permission.create(db, obj_in=obj_in)
|
30 |
+
return user_permission
|
31 |
+
|
32 |
+
|
33 |
+
@router.get("/{id}", response_model=UserPermission)
|
34 |
+
async def get_specific_user_permission(
|
35 |
+
db: Session = Depends(deps.get_db), *, id: int
|
36 |
+
) -> Any:
|
37 |
+
user_permission = crud_user_permission.get(db, id)
|
38 |
+
return user_permission
|
39 |
+
|
40 |
+
|
41 |
+
@router.put("/{id}", response_model=UserPermission)
|
42 |
+
async def update_user_permission(
|
43 |
+
db: Session = Depends(deps.get_db), *, id: int, obj_in: UserPermissionUpdate
|
44 |
+
) -> Any:
|
45 |
+
user_permission = crud_user_permission.get(db, id)
|
46 |
+
user_permission = crud_user_permission.update(
|
47 |
+
db, db_obj=user_permission, obj_in=obj_in
|
48 |
+
)
|
49 |
+
return user_permission
|
api/endpoints/users.py
ADDED
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import secrets
|
2 |
+
from schemas.user import UserCreate
|
3 |
+
from typing import Any, List
|
4 |
+
import aiofiles
|
5 |
+
from hashlib import sha1
|
6 |
+
|
7 |
+
import os
|
8 |
+
|
9 |
+
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, File
|
10 |
+
from fastapi.encoders import jsonable_encoder
|
11 |
+
from pydantic.networks import EmailStr
|
12 |
+
from sqlalchemy.orm import Session
|
13 |
+
from core import settings
|
14 |
+
|
15 |
+
|
16 |
+
import cruds
|
17 |
+
import models
|
18 |
+
import schemas
|
19 |
+
from utils import deps
|
20 |
+
from core.config import settings
|
21 |
+
from utils.utils import send_reset_password_email
|
22 |
+
|
23 |
+
from fastapi import FastAPI, File, Form, UploadFile
|
24 |
+
from typing import List, Optional
|
25 |
+
from datetime import date
|
26 |
+
|
27 |
+
router = APIRouter()
|
28 |
+
|
29 |
+
|
30 |
+
@router.get("/", response_model=List[schemas.user.UserReturn])
|
31 |
+
def read_users(
|
32 |
+
db: Session = Depends(deps.get_db),
|
33 |
+
skip: int = 0,
|
34 |
+
limit: int = 100,
|
35 |
+
current_user: models.User = Depends(deps.get_current_admin_or_above),
|
36 |
+
) -> Any:
|
37 |
+
"""
|
38 |
+
Retrieve users.
|
39 |
+
"""
|
40 |
+
users = cruds.crud_user.get_multi(db, skip=skip, limit=limit)
|
41 |
+
return users
|
42 |
+
|
43 |
+
|
44 |
+
@router.get("/teacher/", response_model=List[schemas.user.TeacherShort])
|
45 |
+
def get_teachers(
|
46 |
+
db: Session = Depends(deps.get_db),
|
47 |
+
skip: int = 0,
|
48 |
+
limit: int = 200,
|
49 |
+
current_user: models.User = Depends(deps.get_current_active_teacher_or_above),
|
50 |
+
) -> Any:
|
51 |
+
teachers = (
|
52 |
+
db.query(models.User)
|
53 |
+
.filter(models.User.user_type == settings.UserType.TEACHER.value)
|
54 |
+
.all()
|
55 |
+
)
|
56 |
+
return teachers
|
57 |
+
|
58 |
+
|
59 |
+
@router.post("/", response_model=schemas.User)
|
60 |
+
async def create_user(
|
61 |
+
*,
|
62 |
+
db: Session = Depends(deps.get_db),
|
63 |
+
user_in: schemas.user.AdminUserCreate,
|
64 |
+
current_user: models.User = Depends(deps.get_current_active_superuser),
|
65 |
+
) -> Any:
|
66 |
+
user = cruds.crud_user.get_by_email(db, email=user_in.email)
|
67 |
+
if user:
|
68 |
+
raise HTTPException(
|
69 |
+
status_code=400,
|
70 |
+
detail="Error ID: 128",
|
71 |
+
) # The user with this username already exists in the system.
|
72 |
+
user_create = schemas.UserCreate(
|
73 |
+
email=user_in.email,
|
74 |
+
full_name=user_in.full_name,
|
75 |
+
address=user_in.address,
|
76 |
+
group_id=user_in.group_id,
|
77 |
+
contact_number=user_in.contact_number,
|
78 |
+
dob=user_in.dob,
|
79 |
+
join_year=user_in.join_year,
|
80 |
+
password=settings.SECRET_KEY,
|
81 |
+
)
|
82 |
+
user = cruds.crud_user.create(db, obj_in=user_create)
|
83 |
+
cruds.crud_user.verify_user(db=db, db_obj=user)
|
84 |
+
await send_reset_password_email(user=user)
|
85 |
+
return user
|
86 |
+
|
87 |
+
|
88 |
+
@router.put("/me/", response_model=schemas.user.UserReturn)
|
89 |
+
async def update_user_me(
|
90 |
+
*,
|
91 |
+
db: Session = Depends(deps.get_db),
|
92 |
+
full_name: Optional[str] = Form(None),
|
93 |
+
address: Optional[str] = Form(None),
|
94 |
+
dob: Optional[date] = Form(None),
|
95 |
+
contact_number: Optional[str] = Form(None),
|
96 |
+
profile_photo: Optional[UploadFile] = File(None),
|
97 |
+
current_user: models.User = Depends(deps.get_current_active_user),
|
98 |
+
) -> Any:
|
99 |
+
"""
|
100 |
+
Update own user.
|
101 |
+
"""
|
102 |
+
profile_db_path = None
|
103 |
+
if profile_photo:
|
104 |
+
profiles_path = os.path.join(settings.UPLOAD_DIR_ROOT, "profiles")
|
105 |
+
content_type = profile_photo.content_type
|
106 |
+
file_extension = content_type[content_type.index("/") + 1 :]
|
107 |
+
new_profile_image = f"{secrets.token_hex(nbytes=16)}.{file_extension}"
|
108 |
+
profile_db_path = os.path.join("profiles", new_profile_image)
|
109 |
+
new_profile_image_file_path = os.path.join(
|
110 |
+
settings.UPLOAD_DIR_ROOT, profile_db_path
|
111 |
+
)
|
112 |
+
|
113 |
+
if not os.path.exists(profiles_path):
|
114 |
+
os.makedirs(profiles_path)
|
115 |
+
|
116 |
+
async with aiofiles.open(new_profile_image_file_path, mode="wb") as f:
|
117 |
+
content = await profile_photo.read()
|
118 |
+
await f.write(content)
|
119 |
+
|
120 |
+
try:
|
121 |
+
if current_user.profile_image != None:
|
122 |
+
os.remove(
|
123 |
+
os.path.join(settings.UPLOAD_DIR_ROOT, current_user.profile_image)
|
124 |
+
)
|
125 |
+
except Exception:
|
126 |
+
pass
|
127 |
+
|
128 |
+
user_in = schemas.UserUpdate(
|
129 |
+
full_name=full_name,
|
130 |
+
address=address,
|
131 |
+
dob=dob,
|
132 |
+
contact_number=contact_number,
|
133 |
+
profile_image=profile_db_path,
|
134 |
+
)
|
135 |
+
print(jsonable_encoder(user_in))
|
136 |
+
|
137 |
+
user = cruds.crud_user.update(
|
138 |
+
db, db_obj=current_user, obj_in=user_in.dict(exclude_none=True)
|
139 |
+
)
|
140 |
+
|
141 |
+
return user
|
142 |
+
|
143 |
+
|
144 |
+
@router.get(
|
145 |
+
"/me/", response_model=schemas.user.UserReturn, response_model_exclude_none=True
|
146 |
+
)
|
147 |
+
# @router.get("/me/teacher_group", response_model=schemas.user.UserReturn)
|
148 |
+
async def read_user_me(
|
149 |
+
db: Session = Depends(deps.get_db),
|
150 |
+
current_user: models.User = Depends(deps.get_current_active_user),
|
151 |
+
) -> Any:
|
152 |
+
"""
|
153 |
+
Get current user.
|
154 |
+
"""
|
155 |
+
return current_user
|
156 |
+
|
157 |
+
|
158 |
+
# @router.put("/me/profile/")
|
159 |
+
# async def update_my_profile_photo(
|
160 |
+
# db: Session = Depends(deps.get_db),
|
161 |
+
# *,
|
162 |
+
# current_user: models.User = Depends(deps.get_current_active_user),
|
163 |
+
# profile_photo: UploadFile = File(...),
|
164 |
+
# ):
|
165 |
+
|
166 |
+
# cruds.crud_user.update(
|
167 |
+
# db,
|
168 |
+
# db_obj=current_user,
|
169 |
+
# obj_in=schemas.user.ImageUpdate(profile_image=profile_db_path),
|
170 |
+
# )
|
171 |
+
|
172 |
+
# return {"msg": "success", "profile": new_profile_image}
|
173 |
+
|
174 |
+
|
175 |
+
@router.get("/{user_id}/", response_model=schemas.user.UserReturn)
|
176 |
+
async def read_user_by_id(
|
177 |
+
user_id: int,
|
178 |
+
current_user: models.User = Depends(deps.get_current_active_user),
|
179 |
+
db: Session = Depends(deps.get_db),
|
180 |
+
) -> Any:
|
181 |
+
"""
|
182 |
+
Get a specific user by id.
|
183 |
+
"""
|
184 |
+
user = cruds.crud_user.get(db, id=user_id)
|
185 |
+
if user == current_user:
|
186 |
+
return user
|
187 |
+
|
188 |
+
if current_user.user_type > settings.UserType.ADMIN.value:
|
189 |
+
raise HTTPException(
|
190 |
+
status_code=400, detail="Error ID: 131"
|
191 |
+
) # The user doesn't have enough privileges
|
192 |
+
# if not cruds.crud_user.is_superuser(current_user):
|
193 |
+
# raise HTTPException(
|
194 |
+
# status_code=400, detail="Error ID: 131"
|
195 |
+
# ) # The user doesn't have enough privileges
|
196 |
+
return user
|
197 |
+
|
198 |
+
|
199 |
+
@router.put("/{user_id}/", response_model=schemas.User)
|
200 |
+
async def update_user(
|
201 |
+
*,
|
202 |
+
db: Session = Depends(deps.get_db),
|
203 |
+
user_id: int,
|
204 |
+
user_in: schemas.UserUpdate,
|
205 |
+
current_user: models.User = Depends(deps.get_current_admin_or_above),
|
206 |
+
) -> Any:
|
207 |
+
"""
|
208 |
+
Update a user.
|
209 |
+
"""
|
210 |
+
user = cruds.crud_user.get(db, id=user_id)
|
211 |
+
if not user:
|
212 |
+
raise HTTPException(
|
213 |
+
status_code=404,
|
214 |
+
detail="Error ID: 132",
|
215 |
+
) # The user with this username does not exist in the system
|
216 |
+
user = cruds.crud_user.update(db, db_obj=user, obj_in=user_in)
|
217 |
+
return user
|
218 |
+
|
219 |
+
|
220 |
+
@router.delete("/{user_id}/")
|
221 |
+
async def delete_user(
|
222 |
+
*,
|
223 |
+
db: Session = Depends(deps.get_db),
|
224 |
+
user_id: int,
|
225 |
+
current_user: models.User = Depends(deps.get_current_admin_or_above),
|
226 |
+
) -> Any:
|
227 |
+
cruds.crud_user.remove(db, id=user_id)
|
228 |
+
return {"msg": "success"}
|
229 |
+
|
230 |
+
|
231 |
+
@router.put("/{user_id}/profile")
|
232 |
+
async def update_profile_photo(
|
233 |
+
db: Session = Depends(deps.get_db),
|
234 |
+
*,
|
235 |
+
user_id: int,
|
236 |
+
current_user: models.User = Depends(deps.get_current_admin_or_above),
|
237 |
+
profile_photo: UploadFile = File(...),
|
238 |
+
):
|
239 |
+
|
240 |
+
user = cruds.crud_user.get_by_id(db, id=user_id)
|
241 |
+
profile_image_path = os.path.join("uploaded_files", "profiles")
|
242 |
+
profile_image = f"{abs(hash(str(user.id)))}.jpg"
|
243 |
+
profile_image_file_path = os.path.join(profile_image_path, profile_image)
|
244 |
+
|
245 |
+
if not os.path.exists(profile_image_path):
|
246 |
+
os.makedirs(profile_image_path)
|
247 |
+
else:
|
248 |
+
if os.path.exists(os.path.join(profile_image_path, f"{user.profile_image}")):
|
249 |
+
os.remove(os.path.join(profile_image_path, f"{user.profile_image}"))
|
250 |
+
|
251 |
+
async with aiofiles.open(profile_image_file_path, mode="wb") as f:
|
252 |
+
content = await profile_photo.read()
|
253 |
+
await f.write(content)
|
254 |
+
|
255 |
+
user = cruds.crud_user.update(
|
256 |
+
db,
|
257 |
+
db_obj=user,
|
258 |
+
obj_in=schemas.UserUpdate(profile_image=profile_image),
|
259 |
+
)
|
260 |
+
|
261 |
+
return user
|
api/endpoints/utils.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
+
from fastapi import APIRouter, Depends
|
4 |
+
from fastapi.responses import FileResponse
|
5 |
+
from pydantic.networks import EmailStr
|
6 |
+
|
7 |
+
import models
|
8 |
+
import schemas
|
9 |
+
from utils import deps
|
10 |
+
|
11 |
+
router = APIRouter()
|
12 |
+
|
13 |
+
|
14 |
+
@router.get("/ping")
|
15 |
+
async def ping() -> Any:
|
16 |
+
return {"msg": "pong"}
|
app.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uvicorn
|
2 |
+
import os
|
3 |
+
from fastapi import FastAPI
|
4 |
+
from fastapi.openapi.docs import (
|
5 |
+
get_swagger_ui_html,
|
6 |
+
get_swagger_ui_oauth2_redirect_html,
|
7 |
+
)
|
8 |
+
from pyngrok import ngrok
|
9 |
+
from starlette.middleware.cors import CORSMiddleware
|
10 |
+
|
11 |
+
from core.config import settings
|
12 |
+
from core.db import (
|
13 |
+
init,
|
14 |
+
redis_cache_client,
|
15 |
+
redis_chat_client,
|
16 |
+
redis_general,
|
17 |
+
redis_session_client,
|
18 |
+
redis_throttle_client,
|
19 |
+
)
|
20 |
+
from api import router
|
21 |
+
|
22 |
+
app = FastAPI(
|
23 |
+
title=settings.PROJECT_NAME,
|
24 |
+
#openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
25 |
+
# docs_url=None,
|
26 |
+
)
|
27 |
+
|
28 |
+
|
29 |
+
@app.on_event("startup")
|
30 |
+
async def startup():
|
31 |
+
await redis_cache_client.initialize()
|
32 |
+
await redis_chat_client.initialize()
|
33 |
+
await redis_throttle_client.initialize()
|
34 |
+
await redis_session_client.initialize()
|
35 |
+
await redis_general.initialize()
|
36 |
+
init.init_db()
|
37 |
+
|
38 |
+
|
39 |
+
@app.on_event("shutdown")
|
40 |
+
async def shutdown():
|
41 |
+
await redis_cache_client.close()
|
42 |
+
await redis_chat_client.close()
|
43 |
+
await redis_throttle_client.close()
|
44 |
+
await redis_session_client.close()
|
45 |
+
await redis_general.close()
|
46 |
+
|
47 |
+
|
48 |
+
"""@app.get("/docs", include_in_schema=False)
|
49 |
+
async def custom_swagger_ui_html():
|
50 |
+
return get_swagger_ui_html(
|
51 |
+
openapi_url=app.openapi_url,
|
52 |
+
title=app.title + " - API Documentaion",
|
53 |
+
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
|
54 |
+
swagger_js_url=f"{settings.STATIC_URL_BASE}/static/swagger-ui-bundle.js",
|
55 |
+
swagger_css_url=f"{settings.STATIC_URL_BASE}/static/swagger-ui.css",
|
56 |
+
)"""
|
57 |
+
|
58 |
+
|
59 |
+
"""@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
|
60 |
+
async def swagger_ui_redirect():
|
61 |
+
return get_swagger_ui_oauth2_redirect_html()"""
|
62 |
+
|
63 |
+
|
64 |
+
if settings.BACKEND_CORS_ORIGINS:
|
65 |
+
app.add_middleware(
|
66 |
+
CORSMiddleware,
|
67 |
+
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
|
68 |
+
allow_credentials=True,
|
69 |
+
allow_methods=["*"],
|
70 |
+
allow_headers=["*"],
|
71 |
+
)
|
72 |
+
pass
|
73 |
+
|
74 |
+
app.include_router(router, prefix=settings.API_V1_STR)
|
75 |
+
|
76 |
+
|
77 |
+
def run():
|
78 |
+
reload_blacklist = ["tests", ".pytest_cache"]
|
79 |
+
reload_dirs = os.listdir()
|
80 |
+
|
81 |
+
for dir in reload_blacklist:
|
82 |
+
try:
|
83 |
+
reload_dirs.remove(dir)
|
84 |
+
except:
|
85 |
+
pass
|
86 |
+
public_url = ngrok.connect(addr=f"http://localhost:{settings.BACKEND_PORT}")
|
87 |
+
print("Public URL:", public_url)
|
88 |
+
uvicorn.run(
|
89 |
+
"app:app",
|
90 |
+
host=settings.BACKEND_HOST,
|
91 |
+
port=settings.BACKEND_PORT,
|
92 |
+
reload=settings.DEV_MODE,
|
93 |
+
reload_dirs=reload_dirs,
|
94 |
+
debug=settings.DEV_MODE,
|
95 |
+
workers=settings.WORKERS,
|
96 |
+
)
|
97 |
+
|
98 |
+
|
99 |
+
if __name__ == "__main__":
|
100 |
+
run()
|
core/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .config import settings
|
core/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (165 Bytes). View file
|
|
core/__pycache__/cache.cpython-310.pyc
ADDED
Binary file (1.16 kB). View file
|
|