ashwinR commited on
Commit
b7a7f32
1 Parent(s): 3aa63b1

Upload 245 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +1 -0
  2. Dockerfile +28 -0
  3. Readme.md +3 -0
  4. __init__.py +0 -0
  5. __pycache__/app.cpython-310.pyc +0 -0
  6. __pycache__/manage.cpython-310.pyc +0 -0
  7. alembic.ini +86 -0
  8. api/__init__.py +1 -0
  9. api/__pycache__/__init__.cpython-310.pyc +0 -0
  10. api/__pycache__/api.cpython-310.pyc +0 -0
  11. api/api.py +48 -0
  12. api/endpoints/__init__.py +0 -0
  13. api/endpoints/__pycache__/__init__.cpython-310.pyc +0 -0
  14. api/endpoints/__pycache__/assignment.cpython-310.pyc +0 -0
  15. api/endpoints/__pycache__/assignment_upload.cpython-310.pyc +0 -0
  16. api/endpoints/__pycache__/auth.cpython-310.pyc +0 -0
  17. api/endpoints/__pycache__/class_session.cpython-310.pyc +0 -0
  18. api/endpoints/__pycache__/course.cpython-310.pyc +0 -0
  19. api/endpoints/__pycache__/department.cpython-310.pyc +0 -0
  20. api/endpoints/__pycache__/group.cpython-310.pyc +0 -0
  21. api/endpoints/__pycache__/personal_note.cpython-310.pyc +0 -0
  22. api/endpoints/__pycache__/program.cpython-310.pyc +0 -0
  23. api/endpoints/__pycache__/quiz.cpython-310.pyc +0 -0
  24. api/endpoints/__pycache__/quiz_answer.cpython-310.pyc +0 -0
  25. api/endpoints/__pycache__/school.cpython-310.pyc +0 -0
  26. api/endpoints/__pycache__/teacher_note.cpython-310.pyc +0 -0
  27. api/endpoints/__pycache__/two_fa.cpython-310.pyc +0 -0
  28. api/endpoints/__pycache__/users.cpython-310.pyc +0 -0
  29. api/endpoints/__pycache__/utils.cpython-310.pyc +0 -0
  30. api/endpoints/assignment.py +188 -0
  31. api/endpoints/assignment_upload.py +235 -0
  32. api/endpoints/auth.py +332 -0
  33. api/endpoints/class_session.py +300 -0
  34. api/endpoints/course.py +75 -0
  35. api/endpoints/department.py +95 -0
  36. api/endpoints/group.py +130 -0
  37. api/endpoints/personal_note.py +195 -0
  38. api/endpoints/program.py +78 -0
  39. api/endpoints/quiz.py +456 -0
  40. api/endpoints/quiz_answer.py +149 -0
  41. api/endpoints/school.py +67 -0
  42. api/endpoints/teacher_note.py +61 -0
  43. api/endpoints/two_fa.py +151 -0
  44. api/endpoints/user_permission.py +49 -0
  45. api/endpoints/users.py +261 -0
  46. api/endpoints/utils.py +16 -0
  47. app.py +100 -0
  48. core/__init__.py +1 -0
  49. core/__pycache__/__init__.cpython-310.pyc +0 -0
  50. 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