feat(markers): implement marker entity & service

This commit is contained in:
2026-07-02 17:43:13 +01:00
parent 3c8df9b328
commit 18626a5db8
10 changed files with 198 additions and 1 deletions

View File

@@ -108,4 +108,11 @@ public class GlobalExceptionHandler {
APIResponse<Void> response = new APIResponse<>(ERROR, ex.getMessage(), null);
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);
}
}

View File

@@ -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()
);
}
}

View File

@@ -0,0 +1,7 @@
package com.ddf.vodsystem.controllers.dto;
import jakarta.validation.constraints.NotBlank;
public record MarkerCreateRequest(
@NotBlank String message
) {}

View File

@@ -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
) {}

View 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;
}

View File

@@ -0,0 +1,7 @@
package com.ddf.vodsystem.exceptions;
public class NotStreaming extends RuntimeException {
public NotStreaming(String message) {
super(message);
}
}

View File

@@ -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);
}

View File

@@ -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);

View 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);
}
}

View File

@@ -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
);