3333import org .springframework .web .server .ResponseStatusException ;
3434
3535import java .io .File ;
36+ import java .io .IOException ;
37+ import java .nio .file .LinkOption ;
38+ import java .nio .file .Path ;
39+ import java .nio .file .Paths ;
3640import java .time .Duration ;
3741import java .time .Instant ;
3842import java .util .Arrays ;
@@ -49,19 +53,17 @@ public class AppointmentEntryEntity implements EntityModelRelation<Long, Appoint
4953 @ Setter (AccessLevel .NONE ) @ Id private Long id ;
5054 private Duration duration ;
5155 private Instant publish ;
52- @ Column (name = "description" , length = 1000 )
53- private String description ;
56+ @ Column (name = "description" , length = 1000 ) private String description ;
5457
55- @ ManyToOne @ JoinColumn (name = "course_appointment_id" , nullable = false ) @ JsonBackReference @ Cascade ( CascadeType . ALL )
56- private CourseEntity course ;
58+ @ ManyToOne @ JoinColumn (name = "course_appointment_id" , nullable = false ) @ JsonBackReference
59+ @ Cascade ( CascadeType . ALL ) private CourseEntity course ;
5760 @ ManyToOne @ JoinColumn (name = "frequent_appointment_id" ) @ JsonBackReference
5861 private @ Nullable FrequentAppointmentEntity frequentAppointment ;
5962
6063 // must be set through extra method to validate integrity
61- @ Getter (AccessLevel .PROTECTED ) @ Setter (AccessLevel .NONE )
62- @ Nullable private String assignmentDescription ;
63- @ Getter (AccessLevel .PROTECTED ) @ Setter (AccessLevel .NONE )
64- @ Nullable private Instant publishAssignment , submitAssignmentUntil ;
64+ @ Getter (AccessLevel .PROTECTED ) @ Setter (AccessLevel .NONE ) @ Nullable private String assignmentDescription ;
65+ @ Getter (AccessLevel .PROTECTED ) @ Setter (AccessLevel .NONE ) @ Nullable
66+ private Instant publishAssignment , submitAssignmentUntil ;
6567
6668 @ ManyToOne @ JsonManagedReference @ JoinColumn (name = "room_id" , referencedColumnName = "id" )
6769 private @ Nullable RoomEntity room ;
@@ -80,6 +82,42 @@ public AppointmentEntryEntity(long id)
8082 this .id = id ;
8183 }
8284
85+ private static @ NotNull File loadFileSave (@ NotNull String uploadPath , @ NotNull String fileName )
86+ {
87+ if (fileName .contains (".." ) || fileName .contains ("/" ) || fileName .contains ("\\ " ))
88+ {
89+ String errorMessage = String .format ("Invalid file name: %s contains illegal path characters." , fileName );
90+ throw new ResponseStatusException (HttpStatus .BAD_REQUEST , errorMessage );
91+ }
92+
93+ Path filePath = Paths .get (uploadPath , fileName );
94+
95+ try
96+ {
97+ Path normalizedPath = filePath .toRealPath (LinkOption .NOFOLLOW_LINKS );
98+
99+ // some security is crucial
100+ if (!normalizedPath .startsWith (uploadPath ))
101+ {
102+ String errorMessage = String .format ("File %s is outside the allowed directory." , fileName );
103+ throw new ResponseStatusException (HttpStatus .FORBIDDEN , errorMessage );
104+ }
105+
106+ File file = normalizedPath .toFile ();
107+ if (!file .exists ())
108+ {
109+ String errorMessage = String .format ("File %s does not exist." , fileName );
110+ throw new ResponseStatusException (HttpStatus .NOT_FOUND , errorMessage );
111+ }
112+
113+ return file ;
114+ } catch (IOException e )
115+ {
116+ String errorMessage = String .format ("Error processing file path for %s." , fileName );
117+ throw new ResponseStatusException (HttpStatus .INTERNAL_SERVER_ERROR , errorMessage , e );
118+ }
119+ }
120+
83121 public @ NotNull Optional <RoomEntity > getRoom ()
84122 {
85123 FrequentAppointmentEntity frequentAppointment = getFrequentAppointment ();
@@ -92,8 +130,7 @@ public AppointmentEntryEntity(long id)
92130
93131 public @ NotNull AssignmentInsightModel getInsight (@ NotNull UserEntity user )
94132 {
95- FileEntity repository = getCourse ().getRepository ();
96- String uploadPath = repository .getFilePath (uploadPath (user .getId ()));
133+ String uploadPath = getUploadPath (user .getId ());
97134 File file = new File (uploadPath );
98135 File [] files = file .listFiles ();
99136 if (!hasSubmitted (user ) || !file .isDirectory () || Objects .isNull (files ) || files .length == 0 )
@@ -115,8 +152,41 @@ public void submitAssignment(long user, @NotNull MultipartFile... files) throws
115152 throw new ResponseStatusException (HttpStatus .UNPROCESSABLE_ENTITY );
116153 }
117154
118- getCourse ().getRepository ().uploadBatch (uploadPath (user ), files );
119- log .info ("User {} has uploaded files to appointment entry {}" , user , getId ());
155+ String uploadPath = getUploadPath (user );
156+ File [] file = new File (uploadPath ).listFiles ();
157+ if (file != null && (file .length + files .length ) > 5 )
158+ {
159+ throw new ResponseStatusException (HttpStatus .PAYLOAD_TOO_LARGE , "The maximum amount of files exceeded." );
160+ }
161+
162+ getCourse ().getRepository ().uploadBatch (uploadPath , files );
163+ log .info ("User {} has uploaded files to appointment entry {}." , user , getId ());
164+ }
165+
166+ public boolean deleteAssignment (long user , String @ NotNull ... files )
167+ {
168+ String uploadPath = uploadPath (user );
169+ File [] toBeDeleted = new File [files .length ];
170+
171+ for (int i = 0 ; i < files .length ; i ++) {toBeDeleted [i ] = loadFileSave (uploadPath , files [i ]);}
172+
173+ boolean allDeleted = true ;
174+ for (File file : toBeDeleted )
175+ {
176+ if (!file .delete ())
177+ {
178+ allDeleted = false ; // If any file fails to delete, mark as false
179+ }
180+ }
181+ log .info ("User {} has deleted files from appointment entry {}." , user , getId ());
182+
183+ return allDeleted ;
184+ }
185+
186+ private @ NotNull String getUploadPath (long user )
187+ {
188+ FileEntity repository = getCourse ().getRepository ();
189+ return repository .getFilePath (uploadPath (user ));
120190 }
121191
122192 public boolean hasSubmitted (@ NotNull UserEntity user )
@@ -149,16 +219,12 @@ public boolean hasSubmitted(@NotNull UserEntity user)
149219 getRoom ().map (RoomEntity ::toModel ).orElse (null ),
150220 this .getDuration ().toMillis (),
151221 this .getDescription (),
152- assignment
153- );
222+ assignment );
154223 }
155224
156- @ Contract (pure = true , value = "-> new" )
157- @ Override public String toString ()
225+ @ Contract (pure = true , value = "-> new" ) @ Override public String toString ()
158226 { // Automatically generated by IntelliJ
159- return "AppointmentEntryEntity{" +
160- "id=" + id +
161- '}' ;
227+ return "AppointmentEntryEntity{" + "id=" + id + '}' ;
162228 }
163229
164230 @ Override public boolean equals (Object o )
@@ -285,14 +351,14 @@ protected long getTimeStamp()
285351 * without a null check.
286352 * Example usage:
287353 * <pre>
288- * {@code
289- * Optional<AssignmentModel> assignment = entity.getAssignment();
290- * assignment.ifPresent(a -> {
291- * System.out.println("Assignment Description: " + a.getDescription());
292- * System.out.println("Submission Deadline: " + a.getSubmitUntil());
293- * });
294- * }
295- * </pre>
354+ * {@code
355+ * Optional<AssignmentModel> assignment = entity.getAssignment();
356+ * assignment.ifPresent(a -> {
357+ * System.out.println("Assignment Description: " + a.getDescription());
358+ * System.out.println("Submission Deadline: " + a.getSubmitUntil());
359+ * });
360+ * }
361+ * </pre>
296362 */
297363 public @ NotNull Optional <AssignmentModel > getAssignment ()
298364 {
0 commit comments