docs(docstrings): add docstrings to public method interfaces (#25)
- Resolves #18 Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
@@ -24,6 +24,11 @@ public class ClipController {
|
||||
this.clipService = clipService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all clips belonging to the authenticated user.
|
||||
*
|
||||
* @return {@code 200 OK} wrapping the user's clips as {@link ClipResponse} DTOs
|
||||
*/
|
||||
@GetMapping("")
|
||||
public ResponseEntity<APIResponse<List<ClipResponse>>> getClips() {
|
||||
List<Clip> clips = clipService.getClipsByUser();
|
||||
@@ -39,6 +44,12 @@ public class ClipController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single clip owned by the authenticated user.
|
||||
*
|
||||
* @param id the ID of the clip to retrieve
|
||||
* @return {@code 200 OK} wrapping the clip as a {@link ClipResponse} DTO
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<ClipResponse>> getClipById(@PathVariable Long id) {
|
||||
Clip clip = clipService.getClipById(id);
|
||||
@@ -52,6 +63,13 @@ public class ClipController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a partial update to a clip's metadata; null fields are left unchanged.
|
||||
*
|
||||
* @param id the ID of the clip to update
|
||||
* @param updateFields the title and/or description to overwrite
|
||||
* @return {@code 200 OK} wrapping the updated clip as a {@link ClipResponse} DTO
|
||||
*/
|
||||
@PatchMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<ClipResponse>> updateClip(@PathVariable Long id,
|
||||
@RequestBody ClipUpdateRequest updateFields) {
|
||||
@@ -63,6 +81,12 @@ public class ClipController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a clip and its associated files.
|
||||
*
|
||||
* @param id the ID of the clip to delete
|
||||
* @return {@code 200 OK} with a confirmation message
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<String>> deleteClip(@PathVariable Long id) {
|
||||
clipService.deleteClip(id);
|
||||
@@ -76,6 +100,13 @@ public class ClipController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the clip's video file inline, supporting HTTP range requests.
|
||||
*
|
||||
* @param id the ID of the clip to download
|
||||
* @return {@code 200 OK} with the video resource, or {@code 404 Not Found} if the file is missing
|
||||
* @throws IOException if the resource's content length cannot be read
|
||||
*/
|
||||
@GetMapping(value = "/{id}/media", produces = MediaType.ALL_VALUE)
|
||||
public ResponseEntity<Resource> downloadClip(@PathVariable Long id) throws IOException {
|
||||
Resource resource = clipService.downloadClip(id);
|
||||
@@ -93,6 +124,13 @@ public class ClipController {
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the clip's thumbnail image with a public cache policy.
|
||||
*
|
||||
* @param id the ID of the clip whose thumbnail to download
|
||||
* @return {@code 200 OK} with the thumbnail resource, or {@code 404 Not Found} if the file is missing
|
||||
* @throws IOException if the resource's last-modified time cannot be read
|
||||
*/
|
||||
@GetMapping("/{id}/thumbnail")
|
||||
public ResponseEntity<Resource> downloadThumbnail(@PathVariable Long id) throws IOException {
|
||||
Resource resource = clipService.downloadThumbnail(id);
|
||||
|
||||
@@ -18,6 +18,12 @@ public class GlobalExceptionHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
private static final String ERROR = "error";
|
||||
|
||||
/**
|
||||
* Handles malformed multipart requests, returning {@code 400 Bad Request}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler({ MultipartException.class })
|
||||
public ResponseEntity<APIResponse<Void>> handleMultipartException(MultipartException ex) {
|
||||
logger.error("MultipartException: {}", ex.getMessage());
|
||||
@@ -25,6 +31,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles requests missing a required file part, returning {@code 400 Bad Request}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler({ MissingServletRequestPartException.class })
|
||||
public ResponseEntity<APIResponse<Void>> handleMissingPart(MissingServletRequestPartException ex) {
|
||||
logger.error("MissingServletRequestPartException: {}", ex.getMessage());
|
||||
@@ -32,6 +44,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unsupported request content types, returning {@code 415 Unsupported Media Type}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class })
|
||||
public ResponseEntity<APIResponse<Void>> handleUnsupportedMediaType(HttpMediaTypeNotSupportedException ex) {
|
||||
logger.error("HttpMediaTypeNotSupportedException: {}", ex.getMessage());
|
||||
@@ -39,6 +57,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles invalid arguments, returning {@code 400 Bad Request} with the exception message.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleIllegalArgument(IllegalArgumentException ex) {
|
||||
logger.error("IllegalArgumentException: {}", ex.getMessage());
|
||||
@@ -46,6 +70,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles lookups for a non-existent job, returning {@code 404 Not Found}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(JobNotFound.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleFileNotFound(JobNotFound ex) {
|
||||
logger.error("JobNotFound: {}", ex.getMessage());
|
||||
@@ -53,6 +83,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles requests for a job that is still running, returning {@code 202 Accepted}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(JobNotFinished.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleJobNotFinished(JobNotFinished ex) {
|
||||
logger.error("JobNotFinished: {}", ex.getMessage());
|
||||
@@ -60,6 +96,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.ACCEPTED).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ffmpeg processing failures, returning {@code 500 Internal Server Error}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(FFMPEGException.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleFFMPEGException(FFMPEGException ex) {
|
||||
logger.error("FFMPEGException: {}", ex.getMessage());
|
||||
@@ -67,6 +109,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles unauthenticated or unauthorized access, returning {@code 401 Unauthorized}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(NotAuthenticated.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleNotAuthenticated(NotAuthenticated ex) {
|
||||
logger.error("NotAuthenticated: {}", ex.getMessage());
|
||||
@@ -74,6 +122,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an unrecognised stream key, returning {@code 404 Not Found}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(KeyNotFound.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleKeyNotFound(KeyNotFound ex) {
|
||||
logger.error("KeyNotFound: {}", ex.getMessage());
|
||||
@@ -81,6 +135,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an attempt to start a stream while one is already active, returning {@code 409 Conflict}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(AlreadyStreaming.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleAlreadyStreaming(AlreadyStreaming ex) {
|
||||
logger.error("AlreadyStreaming: {}", ex.getMessage());
|
||||
@@ -88,6 +148,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file storage failures, returning {@code 500 Internal Server Error}.
|
||||
*
|
||||
* @param e the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(StorageException.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleStorage(StorageException e) {
|
||||
logger.error("Storage operation failed", e);
|
||||
@@ -95,6 +161,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles lookups for a non-existent clip, returning {@code 404 Not Found}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(ClipNotFound.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleClipNotFound(ClipNotFound ex) {
|
||||
logger.error("ClipNotFound: {}", ex.getMessage());
|
||||
@@ -102,6 +174,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a clip whose media file exists but is not readable, returning {@code 503 Service Unavailable}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(ClipMediaUnavailable.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleClipMediaUnavailable(ClipMediaUnavailable ex) {
|
||||
logger.error("ClipMediaUnavailable (clip {}): {}", ex.getClipId(), ex.getMessage());
|
||||
@@ -109,6 +187,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles lookups for a non-existent VoD, returning {@code 404 Not Found}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(VodNotFound.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleVodNotFound(VodNotFound ex) {
|
||||
logger.error("VodNotFound: {}", ex.getMessage());
|
||||
@@ -116,6 +200,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a VoD whose media file exists but is not readable, returning {@code 503 Service Unavailable}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(VodMediaUnavailable.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleVodMediaUnavailable(VodMediaUnavailable ex) {
|
||||
logger.error("VodMediaUnavailable (vod {}): {}", ex.getVodId(), ex.getMessage());
|
||||
@@ -123,6 +213,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles actions that require an active stream when none exists, returning {@code 409 Conflict}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(NotStreaming.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleNotStreaming(NotStreaming ex) {
|
||||
logger.error("NotStreaming: {}", ex.getMessage());
|
||||
@@ -130,6 +226,12 @@ public class GlobalExceptionHandler {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles lookups for a non-existent marker, returning {@code 404 Not Found}.
|
||||
*
|
||||
* @param ex the thrown exception
|
||||
* @return an error {@link APIResponse}
|
||||
*/
|
||||
@ExceptionHandler(MarkerNotFound.class)
|
||||
public ResponseEntity<APIResponse<Void>> handleMarkerNotFound(MarkerNotFound ex) {
|
||||
logger.error("MarkerNotFound: {}", ex.getMessage());
|
||||
|
||||
@@ -28,6 +28,13 @@ public class JobController {
|
||||
this.jobRegistryService = jobRegistryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status and progress of an asynchronous job.
|
||||
*
|
||||
* @param uuid the UUID of the job to look up
|
||||
* @return {@code 200 OK} wrapping the job's state as a {@link JobResponse} DTO
|
||||
* @throws com.ddf.vodsystem.exceptions.JobNotFound if no job with {@code uuid} exists
|
||||
*/
|
||||
@GetMapping("/{uuid}")
|
||||
public ResponseEntity<APIResponse<JobResponse>> getJob(@PathVariable String uuid) {
|
||||
Job job = jobRegistryService.getJob(uuid);
|
||||
@@ -47,6 +54,15 @@ public class JobController {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the output file produced by a completed job.
|
||||
*
|
||||
* @param uuid the UUID of the job whose output to download
|
||||
* @return {@code 200 OK} with the output file as a downloadable resource
|
||||
* @throws com.ddf.vodsystem.exceptions.JobNotFound if no job with {@code uuid} exists
|
||||
* @throws NotReadyException if the job has not yet produced a downloadable file
|
||||
* @throws ClipNotFound if the job's output file no longer exists on disk
|
||||
*/
|
||||
@GetMapping("/{uuid}/download")
|
||||
public ResponseEntity<Resource> downloadJobFile(@PathVariable String uuid) {
|
||||
Job job = jobRegistryService.getJob(uuid);
|
||||
|
||||
@@ -22,6 +22,11 @@ public class MarkerController {
|
||||
this.markerService = markerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all markers belonging to the authenticated user.
|
||||
*
|
||||
* @return {@code 200 OK} wrapping the user's markers as {@link MarkerResponse} DTOs
|
||||
*/
|
||||
@GetMapping("")
|
||||
public ResponseEntity<APIResponse<List<MarkerResponse>>> getMarkers() {
|
||||
List<MarkerResponse> markers = markerService.getUserMarkers().stream()
|
||||
@@ -33,6 +38,12 @@ public class MarkerController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single marker owned by the authenticated user.
|
||||
*
|
||||
* @param id the ID of the marker to retrieve
|
||||
* @return {@code 200 OK} wrapping the marker as a {@link MarkerResponse} DTO
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<MarkerResponse>> getMarkerById(@PathVariable Long id) {
|
||||
Marker marker = markerService.getMarkerById(id);
|
||||
@@ -42,6 +53,12 @@ public class MarkerController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a marker at the current moment on the user's active stream.
|
||||
*
|
||||
* @param request the marker payload carrying the marker message
|
||||
* @return {@code 200 OK} wrapping the created marker as a {@link MarkerResponse} DTO
|
||||
*/
|
||||
@PostMapping("")
|
||||
public ResponseEntity<APIResponse<MarkerResponse>> createMarker(@Valid @RequestBody MarkerCreateRequest request) {
|
||||
Marker marker = markerService.create(request.message());
|
||||
@@ -51,6 +68,12 @@ public class MarkerController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a marker owned by the authenticated user.
|
||||
*
|
||||
* @param id the ID of the marker to delete
|
||||
* @return {@code 200 OK} with a confirmation message
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<String>> deleteMarker(@PathVariable Long id) {
|
||||
markerService.deleteMarker(id);
|
||||
|
||||
@@ -26,6 +26,14 @@ public class MediaController {
|
||||
this.mediaService = mediaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous job to compress an uploaded video file.
|
||||
*
|
||||
* @param file the uploaded video to compress
|
||||
* @param options trim window, resolution, fps, and target size for the output
|
||||
* @return {@code 200 OK} wrapping the new job's UUID for polling
|
||||
* @throws IOException if saving the upload to temporary storage fails
|
||||
*/
|
||||
@PostMapping(value = "/compress", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<APIResponse<UUIDResponse>> compress(
|
||||
@RequestParam("file")MultipartFile file,
|
||||
@@ -40,6 +48,13 @@ public class MediaController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts an asynchronous job to save a section of the user's stream between two timestamps as a VoD.
|
||||
*
|
||||
* @param saveSectionRequest the start/end timestamps and optional title/description
|
||||
* @return {@code 200 OK} wrapping the new job's UUID for polling
|
||||
* @throws IOException if reading the stream segments fails
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
public ResponseEntity<APIResponse<UUIDResponse>> save(
|
||||
@RequestBody SaveSectionRequest saveSectionRequest) throws IOException {
|
||||
@@ -55,6 +70,13 @@ public class MediaController {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous job to save the section of the user's stream between two markers as a VoD.
|
||||
*
|
||||
* @param saveSectionByMarkerRequest the start/end marker IDs and optional title/description
|
||||
* @return {@code 200 OK} wrapping the new job's UUID for polling
|
||||
* @throws IOException if reading the stream segments fails
|
||||
*/
|
||||
@PostMapping("/save/markers")
|
||||
public ResponseEntity<APIResponse<UUIDResponse>> saveByMarkers(
|
||||
@Valid @RequestBody SaveSectionByMarkerRequest saveSectionByMarkerRequest) throws IOException {
|
||||
@@ -70,6 +92,13 @@ public class MediaController {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous job to save the last few seconds of the user's stream as a clip.
|
||||
*
|
||||
* @param clipSectionRequest the clip duration in seconds and optional title/description
|
||||
* @return {@code 200 OK} wrapping the new job's UUID for polling
|
||||
* @throws IOException if reading the stream segments fails
|
||||
*/
|
||||
@PostMapping("/clip")
|
||||
public ResponseEntity<APIResponse<UUIDResponse>> clip(
|
||||
@RequestBody ClipSectionRequest clipSectionRequest) throws IOException {
|
||||
|
||||
@@ -24,6 +24,14 @@ public class StreamController {
|
||||
this.streamService = streamService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new stream for the user owning the given stream key.
|
||||
* <p>
|
||||
* Called by nginx via its {@code on_publish} RTMP callback when a broadcast begins.
|
||||
*
|
||||
* @param streamKey the RTMP stream key identifying the broadcasting user
|
||||
* @return {@code 200 OK} wrapping the newly started {@link Stream}
|
||||
*/
|
||||
// Called by nginx on_publish
|
||||
@PostMapping("/start")
|
||||
public ResponseEntity<APIResponse<Stream>> startStream(@RequestParam("name") String streamKey) {
|
||||
@@ -32,6 +40,14 @@ public class StreamController {
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Stream started", stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the active stream for the user owning the given stream key.
|
||||
* <p>
|
||||
* Called by nginx via its {@code on_publish_done} RTMP callback when a broadcast stops.
|
||||
*
|
||||
* @param streamKey the RTMP stream key identifying the broadcasting user
|
||||
* @return {@code 200 OK} confirming the stream was ended
|
||||
*/
|
||||
// Called by nginx on_publish_done
|
||||
@PostMapping("/stop")
|
||||
public ResponseEntity<APIResponse<Void>> stopStream(@RequestParam("name") String streamKey) {
|
||||
@@ -40,6 +56,14 @@ public class StreamController {
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Stream ended", null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a liveness heartbeat for the active stream, keeping it from being reaped as stale.
|
||||
* <p>
|
||||
* Called periodically by nginx via its {@code on_update} RTMP callback.
|
||||
*
|
||||
* @param streamKey the RTMP stream key identifying the broadcasting user
|
||||
* @return {@code 200 OK} confirming the heartbeat was recorded
|
||||
*/
|
||||
// Called by nginx on_update
|
||||
@PostMapping("/heartbeat")
|
||||
public ResponseEntity<APIResponse<Void>> heartbeat(@RequestParam("name") String streamKey) {
|
||||
@@ -47,12 +71,24 @@ public class StreamController {
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Heartbeat recorded", null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stream history for the given user.
|
||||
*
|
||||
* @param userId the ID of the user whose stream history to retrieve; must be the authenticated user
|
||||
* @return {@code 200 OK} wrapping the user's past and current {@link Stream}s
|
||||
*/
|
||||
@GetMapping("/history/{userId}")
|
||||
public ResponseEntity<APIResponse<List<Stream>>> getStreamHistory(@PathVariable Long userId) {
|
||||
List<Stream> history = streamService.getStreamHistory(userId);
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Stream history retrieved", history));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether the authenticated user is currently streaming.
|
||||
*
|
||||
* @return {@code 200 OK} wrapping a {@link StreamResponse} indicating the live status and, if live,
|
||||
* the active stream's ID
|
||||
*/
|
||||
@GetMapping("/current")
|
||||
public ResponseEntity<APIResponse<StreamResponse>> getCurrentStream() {
|
||||
Optional<Stream> stream = streamService.getActiveStream();
|
||||
|
||||
@@ -27,6 +27,12 @@ public class UserController {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently authenticated user's profile.
|
||||
*
|
||||
* @return {@code 200 OK} wrapping the authenticated {@link User}
|
||||
* @throws NotAuthenticated if no valid session is present
|
||||
*/
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<APIResponse<User>> user() {
|
||||
Optional<User> user = userService.getLoggedInUser();
|
||||
@@ -40,6 +46,17 @@ public class UserController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user from a Google ID token and issues a session JWT.
|
||||
* <p>
|
||||
* The JWT is set as an HttpOnly {@code token} cookie (scoped by request security) and also
|
||||
* returned in the response body.
|
||||
*
|
||||
* @param token the payload carrying the Google ID token to verify
|
||||
* @param request the incoming request, used to decide cookie {@code Secure}/{@code SameSite} flags
|
||||
* @param response the response the session cookie is written to
|
||||
* @return {@code 200 OK} wrapping the issued JWT as a {@link Token}
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<APIResponse<Token>> login(@RequestBody Token token,
|
||||
HttpServletRequest request,
|
||||
@@ -61,6 +78,13 @@ public class UserController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out by clearing the session cookie.
|
||||
*
|
||||
* @param response the response the expired cookie is written to
|
||||
* @param request the incoming request, used to match the original cookie's {@code Secure}/{@code SameSite} flags
|
||||
* @return {@code 200 OK} confirming logout
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<APIResponse<Void>> logout(HttpServletResponse response,
|
||||
HttpServletRequest request) {
|
||||
|
||||
@@ -24,6 +24,11 @@ public class VodController {
|
||||
this.vodService = vodService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all VoDs belonging to the authenticated user.
|
||||
*
|
||||
* @return {@code 200 OK} wrapping the user's VoDs as {@link VodResponse} DTOs
|
||||
*/
|
||||
@GetMapping("")
|
||||
public ResponseEntity<APIResponse<List<VodResponse>>> getVods() {
|
||||
List<VodResponse> vods = vodService.getUserVods().stream()
|
||||
@@ -33,12 +38,25 @@ public class VodController {
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Vods retrieved successfully", vods));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single VoD owned by the authenticated user.
|
||||
*
|
||||
* @param id the ID of the VoD to retrieve
|
||||
* @return {@code 200 OK} wrapping the VoD as a {@link VodResponse} DTO
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<VodResponse>> getVodById(@PathVariable Long id) {
|
||||
VodResponse vod = convertToDTO(vodService.getVodById(id));
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Vod retrieved successfully", vod));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a partial update to a VoD's metadata; null fields are left unchanged.
|
||||
*
|
||||
* @param id the ID of the VoD to update
|
||||
* @param updateFields the title and/or description to overwrite
|
||||
* @return {@code 200 OK} wrapping the updated VoD as a {@link VodResponse} DTO
|
||||
*/
|
||||
@PatchMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<VodResponse>> updateVod(@PathVariable Long id,
|
||||
@RequestBody VodUpdateRequest updateFields) {
|
||||
@@ -46,6 +64,12 @@ public class VodController {
|
||||
return ResponseEntity.ok(new APIResponse<>(SUCCESS, "Vod updated successfully", vod));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a VoD and its associated files.
|
||||
*
|
||||
* @param id the ID of the VoD to delete
|
||||
* @return {@code 200 OK} with a confirmation message
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<APIResponse<String>> deleteVod(@PathVariable Long id) {
|
||||
vodService.deleteVod(id);
|
||||
@@ -53,6 +77,13 @@ public class VodController {
|
||||
"Vod with ID " + id + " has been deleted"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the VoD's video file inline, supporting HTTP range requests.
|
||||
*
|
||||
* @param id the ID of the VoD to download
|
||||
* @return {@code 200 OK} with the video resource, or {@code 404 Not Found} if the file is missing
|
||||
* @throws IOException if the resource's content length cannot be read
|
||||
*/
|
||||
@GetMapping(value = "/{id}/media", produces = MediaType.ALL_VALUE)
|
||||
public ResponseEntity<Resource> downloadVod(@PathVariable Long id) throws IOException {
|
||||
Resource resource = vodService.downloadVod(id);
|
||||
@@ -70,6 +101,13 @@ public class VodController {
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the VoD's thumbnail image with a public cache policy.
|
||||
*
|
||||
* @param id the ID of the VoD whose thumbnail to download
|
||||
* @return {@code 200 OK} with the thumbnail resource, or {@code 404 Not Found} if the file is missing
|
||||
* @throws IOException if the resource's last-modified time cannot be read
|
||||
*/
|
||||
@GetMapping("/{id}/thumbnail")
|
||||
public ResponseEntity<Resource> downloadThumbnail(@PathVariable Long id) throws IOException {
|
||||
Resource resource = vodService.downloadThumbnail(id);
|
||||
|
||||
Reference in New Issue
Block a user