-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFileController.java
More file actions
246 lines (238 loc) · 9.93 KB
/
Copy pathFileController.java
File metadata and controls
246 lines (238 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package com.itasocialacademy.oitassist.filemanager.controller;
import com.itasocialacademy.oitassist.core.web.ErrorResponse;
import com.itasocialacademy.oitassist.filemanager.dao.enums.RelatedEntityType;
import com.itasocialacademy.oitassist.filemanager.dto.request.FileUploadRequestDto;
import com.itasocialacademy.oitassist.filemanager.dto.request.UpdateFileRoleRequestDto;
import com.itasocialacademy.oitassist.filemanager.dto.response.FileResponseDto;
import com.itasocialacademy.oitassist.filemanager.service.interfaces.FileCleanupService;
import com.itasocialacademy.oitassist.filemanager.service.interfaces.FileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Encoding;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/files")
@RequiredArgsConstructor
@Tag(name = "File Manager V1", description = "Operations related to file management")
public class FileController {
private final FileService fileService;
private final FileCleanupService cleanupService;
/**
* Validates and uploads a batch of files, persisting their metadata and linking
* them to the specified entity. Returns the saved file records on success.
*
* @param files the files to upload
* @param requestDto upload context metadata (entity type and optional entity
* ID)
* @return HTTP 201 with the list of persisted file records
*/
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(
summary = "Upload files",
description = """
Uploads one or more files, stores their metadata, and links them to the specified related entity.
""",
requestBody = @RequestBody(
content = @Content(
mediaType = MediaType.MULTIPART_FORM_DATA_VALUE,
encoding = @Encoding(name = "metadata", contentType = "application/json"))))
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "Files uploaded successfully",
content = @Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = FileResponseDto.class)))),
@ApiResponse(
responseCode = "400",
description = "Invalid upload request",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "401",
description = "Unauthorized",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
@PreAuthorize("isAuthenticated()")
public ResponseEntity<List<FileResponseDto>> upload(
@RequestPart("files") List<MultipartFile> files,
@RequestPart("metadata") @Valid FileUploadRequestDto requestDto) {
List<FileResponseDto> response = fileService.upload(files, requestDto);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
/**
* Marks the file record as SOFT_DELETED. The physical file remains in storage
* and will be purged during the next cleanup cycle.
*
* @param id the ID of the file to soft-delete
* @return HTTP 204 on success
*/
@Operation(
summary = "Soft delete file",
description = "Marks the DB record SOFT_DELETED, file remains intact")
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "File deleted successfully"),
@ApiResponse(
responseCode = "403",
description = "Access denied"),
@ApiResponse(
responseCode = "404",
description = "File not found in the DB")
})
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('ADMIN','ORG','USER')")
public ResponseEntity<Void> deleteSoft(@PathVariable Long id) {
fileService.deleteSoft(id);
return ResponseEntity.noContent().build();
}
/**
* Permanently deletes the file from both the physical storage and the database
* by marking the record as HARD_DELETED.
*
* @param id the ID of the file to hard-delete
* @return HTTP 204 on success
*/
@Operation(
summary = "Hard delete file",
description = "Marks the DB record HARD_DELETED, file is deleted")
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "File deleted successfully"),
@ApiResponse(
responseCode = "403",
description = "Access denied"),
@ApiResponse(
responseCode = "404",
description = "File not found in the DB")
})
@DeleteMapping("/{id}/hard")
@PreAuthorize("hasAnyRole('ADMIN','ORG')")
public ResponseEntity<Void> deleteHard(@PathVariable Long id) {
fileService.deleteHard(id);
return ResponseEntity.noContent().build();
}
/**
* Triggers the full file cleanup cycle immediately, without waiting for the
* next scheduled execution. Restricted to administrators.
*
* @return HTTP 204 on success
*/
@Operation(
summary = "Trigger manual file cleanup",
description = "Forces the file cleanup logic to run immediately for orphaned and expired files.")
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "Cleanup triggered successfully"),
@ApiResponse(
responseCode = "403",
description = "Access denied")
})
@DeleteMapping("/cleanup")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> triggerManualCleanup() {
log.info("Admin triggered manual file cleanup.");
cleanupService.runFullCleanup();
return ResponseEntity.noContent().build();
}
/**
* Retrieves all files with status ATTACHED for the specified entity.
*
* @param entityType the type of the related entity
* @param entityId the ID of the related entity
* @return HTTP 200 with the list of attached files (empty list if none found)
*/
@Operation(
summary = "Get files by entity",
description = """
Returns all files with status ATTACHED for the given entity type and ID.
Returns an empty list if no files are found — this is not an error condition.
""")
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Files retrieved successfully. Empty list if none found.",
content = @Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = FileResponseDto.class)))),
@ApiResponse(
responseCode = "400",
description = "Invalid entityType value",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "401",
description = "Unauthorized",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping
@PreAuthorize("hasAnyRole('ADMIN','ORG')")
public ResponseEntity<List<FileResponseDto>> getFiles(
@RequestParam RelatedEntityType entityType,
@RequestParam Long entityId) {
return ResponseEntity.ok(fileService.getFilesByEntity(entityType, entityId));
}
/**
* Updates the role of an attached file.
*
* @param id the ID of the file to update
* @param requestDto the DTO containing the new role
* @return HTTP 200 with the updated file record
*/
@Operation(
summary = "Update file role",
description = "Updates the role of a file that is in ATTACHED state. "
+ "Accessible only by the file uploader or admin.")
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "File role updated successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = FileResponseDto.class))),
@ApiResponse(
responseCode = "400",
description = "Invalid request or file is not in ATTACHED state",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "403",
description = "Access denied"),
@ApiResponse(
responseCode = "404",
description = "File not found in the DB")
})
@PatchMapping("/{id}/role")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<FileResponseDto> updateRole(
@PathVariable Long id,
@Valid @org.springframework.web.bind.annotation.RequestBody UpdateFileRoleRequestDto requestDto) {
return ResponseEntity.ok(fileService.updateRoleGeneral(id, requestDto));
}
}