diff --git a/src/main/java/com/ddf/vodsystem/controllers/GlobalExceptionHandler.java b/src/main/java/com/ddf/vodsystem/controllers/GlobalExceptionHandler.java index d5ecca9..b1f9ba7 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/GlobalExceptionHandler.java +++ b/src/main/java/com/ddf/vodsystem/controllers/GlobalExceptionHandler.java @@ -108,4 +108,11 @@ public class GlobalExceptionHandler { APIResponse response = new APIResponse<>(ERROR, ex.getMessage(), null); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); } + + @ExceptionHandler(NotStreaming.class) + public ResponseEntity> handleNotStreaming(NotStreaming ex) { + logger.error("NotStreaming: {}", ex.getMessage()); + APIResponse response = new APIResponse<>(ERROR, ex.getMessage(), null); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } } \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/controllers/MarkerController.java b/src/main/java/com/ddf/vodsystem/controllers/MarkerController.java new file mode 100644 index 0000000..147978d --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/MarkerController.java @@ -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>> getMarkers() { + List markers = markerService.getUserMarkers().stream() + .map(this::convertToDTO) + .toList(); + + return ResponseEntity.ok( + new APIResponse<>(SUCCESS, "Markers retrieved successfully", markers) + ); + } + + @PostMapping("") + public ResponseEntity> 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() + ); + } +} diff --git a/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerCreateRequest.java b/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerCreateRequest.java new file mode 100644 index 0000000..bcefad8 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerCreateRequest.java @@ -0,0 +1,7 @@ +package com.ddf.vodsystem.controllers.dto; + +import jakarta.validation.constraints.NotBlank; + +public record MarkerCreateRequest( + @NotBlank String message +) {} diff --git a/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerResponse.java b/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerResponse.java new file mode 100644 index 0000000..54d2510 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/dto/MarkerResponse.java @@ -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 +) {} diff --git a/src/main/java/com/ddf/vodsystem/entities/Marker.java b/src/main/java/com/ddf/vodsystem/entities/Marker.java new file mode 100644 index 0000000..5ff9ef7 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/Marker.java @@ -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; +} diff --git a/src/main/java/com/ddf/vodsystem/exceptions/NotStreaming.java b/src/main/java/com/ddf/vodsystem/exceptions/NotStreaming.java new file mode 100644 index 0000000..15cc395 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/exceptions/NotStreaming.java @@ -0,0 +1,7 @@ +package com.ddf.vodsystem.exceptions; + +public class NotStreaming extends RuntimeException { + public NotStreaming(String message) { + super(message); + } +} diff --git a/src/main/java/com/ddf/vodsystem/repositories/MarkerRepository.java b/src/main/java/com/ddf/vodsystem/repositories/MarkerRepository.java new file mode 100644 index 0000000..581d286 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/MarkerRepository.java @@ -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 { + @Query("SELECT m FROM Marker m WHERE m.user = ?1") + List findByUser(User user); +} diff --git a/src/main/java/com/ddf/vodsystem/services/ClipService.java b/src/main/java/com/ddf/vodsystem/services/ClipService.java index 6000245..e0dfb43 100644 --- a/src/main/java/com/ddf/vodsystem/services/ClipService.java +++ b/src/main/java/com/ddf/vodsystem/services/ClipService.java @@ -174,7 +174,7 @@ public class ClipService { Path file = directoryService.resolvePath(path); if (!Files.exists(file)) { - throw new JobNotFound("Thumbnail file not found"); + throw new ClipNotFound("Thumbnail file not found"); } return new FileSystemResource(file); diff --git a/src/main/java/com/ddf/vodsystem/services/MarkerService.java b/src/main/java/com/ddf/vodsystem/services/MarkerService.java new file mode 100644 index 0000000..bb559b0 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/services/MarkerService.java @@ -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 getUserMarkers() { + Optional 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 = 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); + } +} diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index efbb646..4f33216 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -54,4 +54,15 @@ CREATE TABLE IF NOT EXISTS vods ( video_path VARCHAR(255) NOT NULL, thumbnail_path VARCHAR(255) NOT NULL, 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 ); \ No newline at end of file