WIP: Submit transcoding jobs via a HTTP API #6
@ -0,0 +1,26 @@
|
||||
package se.su.dsv.whisperapi;
|
||||
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
import se.su.dsv.whisperapi.core.TransactionRepository;
|
||||
import se.su.dsv.whisperapi.core.Transcription;
|
||||
|
||||
public class JDBCTransactionRepository implements TransactionRepository {
|
||||
private final JdbcClient jdbcClient;
|
||||
|
||||
public JDBCTransactionRepository(JdbcClient jdbcClient) {
|
||||
this.jdbcClient = jdbcClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Transcription transcription) {
|
||||
jdbcClient.sql("""
|
||||
INSERT INTO transcriptions (id, owner, callback_uri, output_format)
|
||||
VALUES (:id, :owner, :callback_uri, :output_format)
|
||||
""")
|
||||
.param("id", transcription.id())
|
||||
.param("owner", transcription.owner().getName())
|
||||
.param("callback_uri", transcription.callbackUri().toString())
|
||||
.param("output_format", transcription.outputFormat().name())
|
||||
.update();
|
||||
}
|
||||
}
|
@ -3,9 +3,13 @@ package se.su.dsv.whisperapi;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import se.su.dsv.whisperapi.core.TransactionRepository;
|
||||
import se.su.dsv.whisperapi.core.TranscriptionService;
|
||||
|
||||
@SpringBootApplication
|
||||
public class WhisperApiApplication {
|
||||
@ -15,10 +19,32 @@ public class WhisperApiApplication {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.securityMatcher("/api/**")
|
||||
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(
|
||||
authorize -> authorize.anyRequest().authenticated())
|
||||
.oauth2Login(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TranscriptionService transcriptionService(TransactionRepository transcriptionRepository) {
|
||||
return new TranscriptionService(transcriptionRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JDBCTransactionRepository jdbcTransactionRepository(JdbcClient jdbcClient) {
|
||||
return new JDBCTransactionRepository(jdbcClient);
|
||||
}
|
||||
}
|
||||
|
57
src/main/java/se/su/dsv/whisperapi/api/ApiController.java
Normal file
57
src/main/java/se/su/dsv/whisperapi/api/ApiController.java
Normal file
@ -0,0 +1,57 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import se.su.dsv.whisperapi.core.CreateTranscription;
|
||||
import se.su.dsv.whisperapi.core.OutputFormat;
|
||||
import se.su.dsv.whisperapi.core.Transcription;
|
||||
import se.su.dsv.whisperapi.core.TranscriptionService;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* For submitting jobs programmatically via a JSON HTTP API.
|
||||
* Submitting jobs programmatically is only available to applications with
|
||||
* shared filesystem access.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(consumes = "application/json", produces = "application/json")
|
||||
public class ApiController {
|
||||
|
||||
private final TranscriptionService transcriptionService;
|
||||
|
||||
public ApiController(TranscriptionService transcriptionService) {
|
||||
this.transcriptionService = transcriptionService;
|
||||
}
|
||||
|
||||
@PostMapping("/api/transcriptions")
|
||||
public TranscriptionCreatedResponse submitTranscriptionJob(
|
||||
Principal owner,
|
||||
@RequestBody CreateTranscriptionRequest createTranscriptionRequest)
|
||||
{
|
||||
try {
|
||||
URI callbackUri = new URI(createTranscriptionRequest.callback());
|
||||
OutputFormat outputFormat = parseOutputFormat(createTranscriptionRequest.outputFormat());
|
||||
CreateTranscription createTranscription = new CreateTranscription(owner, callbackUri, outputFormat);
|
||||
Transcription transcription = transcriptionService.createTranscription(createTranscription);
|
||||
return new TranscriptionCreatedResponse(transcription.id());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new InvalidCallbackUri(createTranscriptionRequest.callback());
|
||||
}
|
||||
}
|
||||
|
||||
private OutputFormat parseOutputFormat(String outputFormat) {
|
||||
return switch (outputFormat) {
|
||||
case "txt" -> OutputFormat.PLAIN_TEXT;
|
||||
case "vtt" -> OutputFormat.VTT;
|
||||
case "srt" -> OutputFormat.SRT;
|
||||
|
||||
case "tsv" -> OutputFormat.TSV;
|
||||
case "json" -> OutputFormat.JSON;
|
||||
default -> throw new InvalidOutputFormat(outputFormat);
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record CreateTranscriptionRequest(
|
||||
@JsonProperty(value = "callback", required = true) String callback,
|
||||
@JsonProperty(value = "outputformat", required = true) String outputFormat)
|
||||
{
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public class InvalidCallbackUri extends ErrorResponseException {
|
||||
public InvalidCallbackUri(String callbackUri) {
|
||||
super(HttpStatus.BAD_REQUEST);
|
||||
setTitle("Invalid callback URI");
|
||||
setType(URI.create("https://gitea.dsv.su.se/DMC/whisper-frontend/wiki/Errors#invalid-callback-uri"));
|
||||
setDetail("The callback '" + callbackUri + "' is not a valid URI");
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public class InvalidOutputFormat extends ErrorResponseException {
|
||||
public InvalidOutputFormat(String outputFormat) {
|
||||
super(HttpStatus.BAD_REQUEST);
|
||||
setType(URI.create("https://gitea.dsv.su.se/DMC/whisper-frontend/wiki/Errors#invalid-output-format"));
|
||||
setTitle("Invalid output format");
|
||||
setDetail("The output format '" + outputFormat + "' is not supported.");
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record TranscriptionCreatedResponse(
|
||||
@JsonProperty(value = "id", required = true) UUID id)
|
||||
{
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
|
||||
public record CreateTranscription(Principal owner, URI callbackUri, OutputFormat outputFormat) {
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
public enum OutputFormat {
|
||||
PLAIN_TEXT,
|
||||
VTT,
|
||||
SRT,
|
||||
TSV,
|
||||
JSON
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
public interface TransactionRepository {
|
||||
void save(Transcription transcription);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.UUID;
|
||||
|
||||
public record Transcription(UUID id, Principal owner, URI callbackUri, OutputFormat outputFormat) {
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class TranscriptionService {
|
||||
private final TransactionRepository transactionRepository;
|
||||
|
||||
public TranscriptionService(TransactionRepository transactionRepository) {
|
||||
this.transactionRepository = transactionRepository;
|
||||
}
|
||||
|
||||
|
||||
public Transcription createTranscription(CreateTranscription createTranscription) {
|
||||
UUID id = UUID.randomUUID();
|
||||
Transcription transcription = new Transcription(
|
||||
id,
|
||||
createTranscription.owner(),
|
||||
createTranscription.callbackUri(),
|
||||
createTranscription.outputFormat());
|
||||
transactionRepository.save(transcription);
|
||||
return transcription;
|
||||
}
|
||||
}
|
@ -12,3 +12,7 @@ spring.security.oauth2.client.provider.su.authorization-uri=${OAUTH2_AUTH_URI}
|
||||
spring.security.oauth2.client.provider.su.token-uri=${OAUTH2_TOKEN_URI}
|
||||
spring.security.oauth2.client.provider.su.user-info-uri=${OAUTH2_USER_INFO_URI}
|
||||
spring.security.oauth2.client.provider.su.user-name-attribute=sub
|
||||
spring.mvc.problemdetails.enabled=true
|
||||
# Disable logging of resolved exceptions or every ErrorResponseException (ProblemDetails)
|
||||
# that is correctly handled will be logged as a warning
|
||||
spring.mvc.log-resolved-exception=false
|
||||
|
8
src/main/resources/db/migration/V1__transcriptions.sql
Normal file
8
src/main/resources/db/migration/V1__transcriptions.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE transcriptions (
|
||||
id UUID NOT NULL,
|
||||
owner VARCHAR(255) NOT NULL,
|
||||
callback_uri VARCHAR(255),
|
||||
output_format VARCHAR(32) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
INDEX I_transcriptions_owner (owner)
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user
This response should contain a reference to the upload URL for this job.
Fixed in
aa49f418d2