feat(streaming): timestamp based stream markers #6
@@ -108,4 +108,11 @@ public class GlobalExceptionHandler {
|
|||||||
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
|
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NotStreaming.class)
|
||||||
|
public ResponseEntity<APIResponse<Void>> handleNotStreaming(NotStreaming ex) {
|
||||||
|
logger.error("NotStreaming: {}", ex.getMessage());
|
||||||
|
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.ddf.vodsystem.controllers;
|
||||||
|
|
||||||
|
import com.ddf.vodsystem.controllers.dto.MarkerCreateRequest;
|
||||||
|
import com.ddf.vodsystem.controllers.dto.MarkerResponse;
|
||||||
|
import com.ddf.vodsystem.dto.APIResponse;
|
||||||
|
import com.ddf.vodsystem.entities.Marker;
|
||||||
|
import com.ddf.vodsystem.services.MarkerService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/markers")
|
||||||
|
public class MarkerController {
|
||||||
|
private static final String SUCCESS = "success";
|
||||||
|
|
||||||
|
private final MarkerService markerService;
|
||||||
|
|
||||||
|
public MarkerController(MarkerService markerService) {
|
||||||
|
this.markerService = markerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("")
|
||||||
|
public ResponseEntity<APIResponse<List<MarkerResponse>>> getMarkers() {
|
||||||
|
List<MarkerResponse> markers = markerService.getUserMarkers().stream()
|
||||||
|
.map(this::convertToDTO)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
new APIResponse<>(SUCCESS, "Markers retrieved successfully", markers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("")
|
||||||
|
public ResponseEntity<APIResponse<MarkerResponse>> createMarker(@Valid @RequestBody MarkerCreateRequest request) {
|
||||||
|
Marker marker = markerService.create(request.message());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
new APIResponse<>(SUCCESS, "Marker created successfully", convertToDTO(marker))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MarkerResponse convertToDTO(Marker marker) {
|
||||||
|
return new MarkerResponse(
|
||||||
|
marker.getId(),
|
||||||
|
marker.getUser().getId(),
|
||||||
|
marker.getStream().getId(),
|
||||||
|
marker.getMessage(),
|
||||||
|
marker.getTimestamp()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.ddf.vodsystem.controllers.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record MarkerCreateRequest(
|
||||||
|
@NotBlank String message
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.ddf.vodsystem.controllers.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record MarkerResponse(
|
||||||
|
Long id,
|
||||||
|
Long userId,
|
||||||
|
Long streamId,
|
||||||
|
String message,
|
||||||
|
Instant timestamp
|
||||||
|
) {}
|
||||||
33
src/main/java/com/ddf/vodsystem/entities/Marker.java
Normal file
33
src/main/java/com/ddf/vodsystem/entities/Marker.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.ddf.vodsystem.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "markers")
|
||||||
|
@Data
|
||||||
|
public class Marker {
|
||||||
|
@Id
|
||||||
|
@Column(name = "id", nullable = false, unique = true)
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JsonIgnore
|
||||||
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JsonIgnore
|
||||||
|
@JoinColumn(name = "stream_id", nullable = false)
|
||||||
|
private Stream stream;
|
||||||
|
|
||||||
|
@Column(name = "message", length = 255)
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Column(name = "timestamp")
|
||||||
|
private Instant timestamp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.ddf.vodsystem.exceptions;
|
||||||
|
|
||||||
|
public class NotStreaming extends RuntimeException {
|
||||||
|
public NotStreaming(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ddf.vodsystem.repositories;
|
||||||
|
|
||||||
|
import com.ddf.vodsystem.entities.Marker;
|
||||||
|
import com.ddf.vodsystem.entities.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface MarkerRepository extends JpaRepository<Marker, Long> {
|
||||||
|
@Query("SELECT m FROM Marker m WHERE m.user = ?1")
|
||||||
|
List<Marker> findByUser(User user);
|
||||||
|
}
|
||||||
@@ -174,7 +174,7 @@ public class ClipService {
|
|||||||
Path file = directoryService.resolvePath(path);
|
Path file = directoryService.resolvePath(path);
|
||||||
|
|
||||||
if (!Files.exists(file)) {
|
if (!Files.exists(file)) {
|
||||||
throw new JobNotFound("Thumbnail file not found");
|
throw new ClipNotFound("Thumbnail file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FileSystemResource(file);
|
return new FileSystemResource(file);
|
||||||
|
|||||||
52
src/main/java/com/ddf/vodsystem/services/MarkerService.java
Normal file
52
src/main/java/com/ddf/vodsystem/services/MarkerService.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package com.ddf.vodsystem.services;
|
||||||
|
|
||||||
|
import com.ddf.vodsystem.entities.Marker;
|
||||||
|
import com.ddf.vodsystem.entities.Stream;
|
||||||
|
import com.ddf.vodsystem.entities.User;
|
||||||
|
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
||||||
|
import com.ddf.vodsystem.exceptions.NotStreaming;
|
||||||
|
import com.ddf.vodsystem.repositories.MarkerRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MarkerService {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final StreamService streamService;
|
||||||
|
private final MarkerRepository markerRepository;
|
||||||
|
|
||||||
|
public MarkerService(UserService userService, StreamService streamService, MarkerRepository markerRepository) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.streamService = streamService;
|
||||||
|
this.markerRepository = markerRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Marker> getUserMarkers() {
|
||||||
|
Optional<User> user = userService.getLoggedInUser();
|
||||||
|
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
throw new NotAuthenticated("Must be logged in to get markers");
|
||||||
|
}
|
||||||
|
|
||||||
|
return markerRepository.findByUser(user.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Marker create(String message) {
|
||||||
|
Optional<Stream> stream = streamService.getActiveStream();
|
||||||
|
|
||||||
|
if (stream.isEmpty()) {
|
||||||
|
throw new NotStreaming("User must be streaming to mark");
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker marker = new Marker();
|
||||||
|
marker.setStream(stream.get());
|
||||||
|
marker.setUser(stream.get().getUser());
|
||||||
|
marker.setMessage(message);
|
||||||
|
marker.setTimestamp(Instant.now());
|
||||||
|
return markerRepository.saveAndFlush(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,3 +55,14 @@ CREATE TABLE IF NOT EXISTS vods (
|
|||||||
thumbnail_path VARCHAR(255) NOT NULL,
|
thumbnail_path VARCHAR(255) NOT NULL,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS markers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
stream_id BIGINT NOT NULL,
|
||||||
|
message VARCHAR(255),
|
||||||
|
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user