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
|
|
|