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.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Bean;
|
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.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import se.su.dsv.whisperapi.core.TransactionRepository;
|
||||||
|
import se.su.dsv.whisperapi.core.TranscriptionService;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class WhisperApiApplication {
|
public class WhisperApiApplication {
|
||||||
@ -15,10 +19,32 @@ public class WhisperApiApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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 {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http.authorizeHttpRequests(
|
http.authorizeHttpRequests(
|
||||||
authorize -> authorize.anyRequest().authenticated())
|
authorize -> authorize.anyRequest().authenticated())
|
||||||
.oauth2Login(Customizer.withDefaults());
|
.oauth2Login(Customizer.withDefaults());
|
||||||
return http.build();
|
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.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-info-uri=${OAUTH2_USER_INFO_URI}
|
||||||
spring.security.oauth2.client.provider.su.user-name-attribute=sub
|
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