Coverage for server / routes / student / student_classes.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-10-04 09:33 +0000

1from typing import Dict, Optional 

2from fastapi import APIRouter, Body, Depends, HTTPException, Request, status 

3from server.authentication.auth0_bearer import Auth0Bearer 

4from server.services.student.student_classes import StudentClassesService 

5from server.utilities.model_parser import normalize_query_params 

6from server.models.classes import StudentClassListResponse 

7from server.models.classes import StudentFetchbyclasscodeResponse 

8from server.validators.class_code_validator import ClassCodePath 

9 

10router = APIRouter() 

11 

12student_classes_service = StudentClassesService() 

13 

14 

15@router.get( 

16 "/all/fetch", 

17 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

18 status_code=status.HTTP_200_OK, 

19 description="As a student, I can fetch all classes I am enrolled in", 

20 summary="As a student, I can fetch all classes I am enrolled in", 

21 response_model=StudentClassListResponse, 

22 response_model_exclude_none=True, 

23) 

24async def all_classes_fetch( 

25 request: Request, 

26 page_num: int = 1, 

27 page_size: int = 10, 

28 normalized_params: Dict[str, Optional[str]] = Depends(normalize_query_params), 

29) -> dict: 

30 """ 

31 Retrieve all classes for a student with filtering and pagination. 

32 

33 Args: 

34 request (Request): The incoming request object containing student context 

35 page_num (int, optional): Page number for pagination. Defaults to 1. 

36 page_size (int, optional): Number of items per page. Defaults to 10. 

37 normalized_params (Dict[str, Optional[str]]): Query parameters for filtering 

38 - title: Filter by class title (case-insensitive partial match) 

39 - status: Filter by enrollment status ("Enrolled", "Pending", or "Removed") 

40 

41 Returns: 

42 dict: { 

43 "data": List[ClassModel], # Classes for current page 

44 "count": int, # Items in current page 

45 "total": int, # Total classes for student 

46 "page": int, # Current page number 

47 "no_of_pages": int # Total pages 

48 } 

49 

50 Raises: 

51 HTTPException(400): If invalid status provided 

52 HTTPException(500): For server errors 

53 """ 

54 return await student_classes_service.all_classes_fetch( 

55 request, page_num, page_size, normalized_params 

56 ) 

57 

58 

59@router.get( 

60 "/{class_code}/fetch", 

61 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

62 status_code=status.HTTP_200_OK, 

63 description="As a student, I can fetch a class by its code", 

64 summary="As a student, I can fetch a class by its code", 

65 response_model=StudentFetchbyclasscodeResponse, 

66 response_model_exclude_none=True, 

67) 

68async def student_class_fetch(request: Request, class_code: ClassCodePath) -> dict: 

69 """ 

70 Retrieve details of a specific class by its class code. 

71 

72 Args: 

73 request (Request): The incoming request object containing student context 

74 class_code (str): Unique code identifying the class to fetch 

75 

76 Returns: 

77 dict: { 

78 "Class": ClassModel, # Class details if found 

79 "message": str # Status message if class not found 

80 } 

81 

82 Raises: 

83 HTTPException(400): If invalid class code format 

84 HTTPException(500): For server errors 

85 """ 

86 return await student_classes_service.class_fetch(request, class_code) 

87 

88 

89@router.patch( 

90 "/{class_code}/join", 

91 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

92 status_code=status.HTTP_200_OK, 

93 description="As a student, I can join a class using its code", 

94 summary="As a student, I can join a class using its code", 

95) 

96async def class_join(class_code: ClassCodePath, request: Request) -> dict: 

97 """ 

98 Join a class using its code. 

99 

100 This endpoint allows students to join a class by providing a valid class code. The code is 

101 validated and if valid, the student is enrolled in the class. Students can only join classes 

102 that are currently active and accepting new enrollments. 

103 

104 Args: 

105 class_code (str): Unique code identifying the class to join 

106 - Must be a valid, active class code 

107 - Case-sensitive alphanumeric string 

108 request (Request): The incoming request object containing: 

109 - JWT token in Authorization header 

110 - Student authentication context 

111 - Student role and permissions 

112 

113 Returns: 

114 dict: Response containing: 

115 - status (str): Success/failure status 

116 - message (str): Descriptive message 

117 - class_details (dict): Information about the joined class 

118 - name (str): Class name 

119 - teacher (str): Teacher's name 

120 - schedule (str): Class schedule 

121 - location (str): Class location 

122 

123 Raises: 

124 HTTPException: 

125 - 400: If class code is invalid or expired 

126 - 401: If authentication token is invalid 

127 - 403: If user lacks student permissions 

128 - 409: If student is already enrolled 

129 - 422: If class is full or not accepting students 

130 """ 

131 return await student_classes_service.join(class_code, request) 

132 

133 

134@router.patch( 

135 "/{class_code}/leave", 

136 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

137 status_code=status.HTTP_200_OK, 

138 description="As a student, I can request to leave a class", 

139 summary="As a student, I can request to leave a class", 

140) 

141async def class_leave_request(class_code: ClassCodePath, request: Request) -> dict: 

142 """Submit a request to leave a class. 

143 

144 This endpoint allows students to request removal from a class they are currently enrolled in. 

145 The request will need to be approved by the teacher before the student is officially removed. 

146 

147 Args: 

148 class_code (str): The unique code identifying the class 

149 request (Request): The FastAPI request object containing the authenticated student's information 

150 

151 Returns: 

152 dict: A response containing the status of the leave request 

153 Example: {"status": "success", "message": "Leave request submitted successfully"} 

154 

155 Raises: 

156 HTTPException: 

157 - 404 if the class is not found 

158 - 400 if the student is not enrolled in the class 

159 - 409 if a leave request is already pending 

160 """ 

161 return await student_classes_service.leave(class_code, request) 

162 

163 

164@router.patch( 

165 "/join/{class_code}/cancel", 

166 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

167 status_code=status.HTTP_200_OK, 

168 description="As a student, I can cancel a pending request to join a class", 

169 summary="As a student, I can cancel a pending request to join a class", 

170) 

171async def join_cancel(class_code: ClassCodePath, request: Request) -> dict: 

172 """ 

173 Cancel a pending request to join a class. 

174 

175 This endpoint allows students to cancel their outstanding join request for a specific class. 

176 The request will be removed from the pending queue and the student will remain unenrolled. 

177 

178 Args: 

179 class_code (str): The unique code identifying the class 

180 request (Request): The FastAPI request object containing: 

181 - JWT token in Authorization header 

182 - Student authentication context 

183 - Student role and permissions 

184 

185 Returns: 

186 dict: Response containing cancellation status 

187 Example: { 

188 "status": "success", 

189 "message": "Join request cancelled successfully" 

190 } 

191 

192 Raises: 

193 HTTPException: 

194 - 404: If the class code is not found 

195 - 400: If no pending join request exists 

196 - 401: If authentication token is invalid 

197 - 403: If user lacks student permissions 

198 """ 

199 return await student_classes_service.join_cancel(class_code, request) 

200 

201 

202@router.get( 

203 "/messages/{class_uuid}/fetch", 

204 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))], 

205 status_code=status.HTTP_200_OK, 

206 description="As a student, I can fetch announcements for a class I am enrolled in", 

207 summary="As a student, I can fetch class announcements", 

208) 

209async def class_messages_fetch( 

210 request: Request, 

211 class_uuid: str, 

212) -> dict: 

213 """ 

214 Retrieve announcements for a specific class the student is enrolled in. 

215 

216 Args: 

217 request (Request): The incoming request object containing student context 

218 class_uuid (str): Unique identifier for the class 

219 

220 Returns: 

221 dict: { 

222 "messages": list of ClassMessage objects, 

223 "count": int 

224 } 

225 

226 Raises: 

227 HTTPException: 

228 - 400: Invalid class UUID format 

229 - 401: Invalid or missing authentication token 

230 - 404: Class not found or student not enrolled 

231 """ 

232 return await student_classes_service.messages_fetch(request, class_uuid)