Initial commit
This commit is contained in:
190
backend/api/handlers.go
Normal file
190
backend/api/handlers.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"codeberg.org/pluja/whishper/models"
|
||||
)
|
||||
|
||||
func (s *Server) handleGetAllTranscriptions(c *fiber.Ctx) error {
|
||||
transcriptions := s.Db.GetAllTranscriptions()
|
||||
|
||||
// Convert the transcriptions to JSON.
|
||||
json, err := json.Marshal(transcriptions)
|
||||
if err != nil {
|
||||
// 503 On vacation!
|
||||
return fiber.NewError(fiber.StatusServiceUnavailable, "On vacation!")
|
||||
}
|
||||
|
||||
// Write the JSON to the response body.
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Write(json)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleGetTranscriptionById(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
t := s.Db.GetTranscription(id)
|
||||
if t == nil {
|
||||
log.Warn().Msgf("Transcription with id %v not found", id)
|
||||
return fiber.NewError(fiber.StatusNotFound, "Not found")
|
||||
}
|
||||
|
||||
// Convert the transcription to JSON.
|
||||
json, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
// 503 On vacation!
|
||||
return fiber.NewError(fiber.StatusServiceUnavailable, "On vacation!")
|
||||
}
|
||||
|
||||
// Write the JSON to the response body.
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Write(json)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function receives data from a form to create a new transcription.
|
||||
// If the transcription is created successfully, it returns a 201 Created status code and
|
||||
// broadcasts the new transcription to all ws clients.
|
||||
func (s *Server) handlePostTranscription(c *fiber.Ctx) error {
|
||||
log.Debug().Msg("POST /api/transcriptions")
|
||||
var transcription models.Transcription
|
||||
|
||||
// we get the filename from the from
|
||||
var filename string
|
||||
if c.FormValue("sourceUrl") == "" {
|
||||
// Get the form file from the request.
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error getting file field from the form")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Bad request")
|
||||
}
|
||||
timeid := time.Now().Format("2006_01_02-150405000")
|
||||
filename = timeid + models.FileNameSeparator + file.Filename
|
||||
// if it's empty and there is no sourceurl we set a timestamp-based filename
|
||||
if filename == timeid+models.FileNameSeparator {
|
||||
filename = timeid + models.FileNameSeparator + time.Now().Format("2006_01_02-150405")
|
||||
}
|
||||
|
||||
// Save the file to the uploads directory.
|
||||
err = c.SaveFile(file, fmt.Sprintf("%v/%v", os.Getenv("UPLOAD_DIR"), filename))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error saving the form file to disk into %v", os.Getenv("UPLOAD_DIR"))
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the body into the transcription struct.
|
||||
transcription.Language = c.FormValue("language")
|
||||
transcription.ModelSize = c.FormValue("modelSize")
|
||||
transcription.FileName = filename
|
||||
transcription.Status = models.TranscriptionStatusPending
|
||||
transcription.Task = "transcribe"
|
||||
transcription.SourceUrl = c.FormValue("sourceUrl")
|
||||
// TODO: Implement other device types
|
||||
transcription.Device = "cpu"
|
||||
|
||||
log.Debug().Msgf("Transcription: %+v", transcription)
|
||||
// Save transcription to database
|
||||
res, err := s.Db.NewTranscription(&transcription)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error saving transcription to database")
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
// Write the JSON to the response body.
|
||||
s.BroadcastTranscription(res)
|
||||
s.NewTranscriptionCh <- true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteTranscription(c *fiber.Ctx) error {
|
||||
// First get the transcription from the database
|
||||
id := c.Params("id")
|
||||
t := s.Db.GetTranscription(id)
|
||||
if t == nil {
|
||||
log.Warn().Msgf("Transcription with id %v not found", id)
|
||||
return fiber.NewError(fiber.StatusNotFound, "Not found")
|
||||
}
|
||||
|
||||
// Then delete the file from disk
|
||||
err := os.Remove(fmt.Sprintf("%v/%v", os.Getenv("UPLOAD_DIR"), t.FileName))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error deleting file %v", t.FileName)
|
||||
}
|
||||
|
||||
// Finally delete the transcription from the database
|
||||
err = s.Db.DeleteTranscription(id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error deleting transcription %v", id)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
// Return status deleted
|
||||
c.Status(fiber.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handlePatchTranscription(c *fiber.Ctx) error {
|
||||
var transcription models.Transcription
|
||||
// Parse the body into the transcription struct.
|
||||
err := json.Unmarshal(c.Body(), &transcription)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error parsing JSON body")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Bad request")
|
||||
}
|
||||
|
||||
// Update the transcription in the database
|
||||
ut, err := s.Db.UpdateTranscription(&transcription)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error updating transcription")
|
||||
if err.Error() == "no documents were modified" {
|
||||
return fiber.NewError(fiber.StatusNotModified, "Not found")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
// Write the JSON to the response body.
|
||||
s.BroadcastTranscription(ut)
|
||||
|
||||
// Return status ok
|
||||
json, err := json.Marshal(&ut)
|
||||
if err != nil {
|
||||
// 503 On vacation!
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Error parsing json!")
|
||||
}
|
||||
|
||||
c.Status(fiber.StatusOK)
|
||||
c.Write(json)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleTranslate(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
targetLang := c.Params("target")
|
||||
|
||||
transcription := s.Db.GetTranscription(id)
|
||||
|
||||
// Set status as translating
|
||||
transcription.Status = models.TrannscriptionStatusTranslating
|
||||
s.Db.UpdateTranscription(transcription)
|
||||
s.BroadcastTranscription(transcription)
|
||||
|
||||
err := transcription.Translate(targetLang)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Error with translation")
|
||||
return err
|
||||
}
|
||||
|
||||
// Set as done
|
||||
transcription.Status = models.TranscriptionStatusDone
|
||||
s.Db.UpdateTranscription(transcription)
|
||||
s.BroadcastTranscription(transcription)
|
||||
return nil
|
||||
}
|
||||
158
backend/api/server.go
Normal file
158
backend/api/server.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"codeberg.org/pluja/whishper/database"
|
||||
"codeberg.org/pluja/whishper/models"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ListenAddr string
|
||||
Router *fiber.App
|
||||
Db database.Db
|
||||
NewTranscriptionCh chan bool
|
||||
clients []*websocket.Conn
|
||||
}
|
||||
|
||||
func NewServer(listenAddr string, db database.Db) *Server {
|
||||
return &Server{
|
||||
ListenAddr: listenAddr,
|
||||
Router: fiber.New(fiber.Config{
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
BodyLimit: 100 * 1024 * 1024, // Increase body limit to 100MB
|
||||
ServerHeader: "Fiber", // Optional, for easier debugging
|
||||
}),
|
||||
Db: db,
|
||||
clients: make([]*websocket.Conn, 0),
|
||||
NewTranscriptionCh: make(chan bool, 100),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Run() {
|
||||
s.SetupWebsocket()
|
||||
s.SetupMiddleware()
|
||||
s.RegisterRoutes()
|
||||
s.Router.Listen(s.ListenAddr)
|
||||
}
|
||||
|
||||
func (s *Server) SetupWebsocket() {
|
||||
s.Router.Get("/ws/transcriptions", websocket.New(func(c *websocket.Conn) {
|
||||
|
||||
// Add this connection to the slice of clients
|
||||
s.clients = append(s.clients, c)
|
||||
|
||||
for {
|
||||
_, msg, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
// Check for normal close error (1000) or going away error (1001)
|
||||
if err.Error() != "websocket: close 1000 (normal)" &&
|
||||
err.Error() != "websocket: close 1001 (going away)" {
|
||||
log.Error().Err(err).Msgf("Error reading message")
|
||||
}
|
||||
// Remove the client from the slice if it has disconnected
|
||||
s.clients = removeWsClient(s.clients, c)
|
||||
return
|
||||
}
|
||||
s.handleWebsocketMessage(c, msg)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *Server) BroadcastTranscription(t *models.Transcription) {
|
||||
// Convert the transcription to JSON.
|
||||
json, err := json.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error marshalling transcription to JSON:")
|
||||
return
|
||||
}
|
||||
for _, client := range s.clients {
|
||||
if err := client.WriteMessage(websocket.TextMessage, json); err != nil {
|
||||
log.Error().Err(err).Msg("Error broadcasting message:")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SetupMiddleware() {
|
||||
s.Router.Use(cors.New())
|
||||
}
|
||||
|
||||
func (s *Server) RegisterRoutes() {
|
||||
// Static routes
|
||||
s.Router.Static("/api/video", os.Getenv("UPLOAD_DIR"))
|
||||
|
||||
// Register HTTP route for getting initial state.
|
||||
s.Router.Get("/api/transcriptions", func(c *fiber.Ctx) error {
|
||||
err := s.handleGetAllTranscriptions(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling POST /api/transcriptions")
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Register HTTP route for getting initial state.
|
||||
s.Router.Get("/api/transcriptions/:id", func(c *fiber.Ctx) error {
|
||||
log.Debug().Msgf("GET /api/transcriptions/%v", c.Params("id"))
|
||||
err := s.handleGetTranscriptionById(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling GET /api/transcriptions/:id")
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Register HTTP route for getting initial state.
|
||||
s.Router.Get("/api/translate/:id/:target", func(c *fiber.Ctx) error {
|
||||
log.Debug().Msgf("GET /api/translate/%v/%v", c.Params("id"), c.Params("target"))
|
||||
err := s.handleTranslate(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling GET /api/translate/:id/:source")
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Register HTTP route for receiving the form data and creating new transcription job.
|
||||
s.Router.Post("/api/transcriptions", func(c *fiber.Ctx) error {
|
||||
log.Debug().Msg("POST /api/transcriptions")
|
||||
err := s.handlePostTranscription(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling POST /api/transcriptions")
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
s.Router.Patch("/api/transcriptions", func(c *fiber.Ctx) error {
|
||||
//log.Debug().Msgf("PATCH /api/transcriptions/%v", c.Params("id"))
|
||||
err := s.handlePatchTranscription(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling PATCH /api/transcriptions")
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Register HTTP route for receiving the form data and creating new transcription job.
|
||||
s.Router.Delete("/api/transcriptions/:id", func(c *fiber.Ctx) error {
|
||||
log.Debug().Msgf("DELETE /api/transcriptions/%v", c.Params("id"))
|
||||
err := s.handleDeleteTranscription(c)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error handling DELETE /api/transcriptions")
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to remove a WebSocket connection from the slice
|
||||
func removeWsClient(s []*websocket.Conn, r *websocket.Conn) []*websocket.Conn {
|
||||
for i, v := range s {
|
||||
if v == r {
|
||||
return append(s[:i], s[i+1:]...)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
41
backend/api/websocket.go
Normal file
41
backend/api/websocket.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/rs/zerolog/log"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"codeberg.org/pluja/whishper/models"
|
||||
)
|
||||
|
||||
func (s *Server) handleWebsocketMessage(wsess *websocket.Conn, msg []byte) {
|
||||
log.Info().Msgf("Received message from client: %v", wsess.RemoteAddr().String())
|
||||
// Try to unmarshal message to transcription
|
||||
var transcription models.Transcription
|
||||
err := json.Unmarshal(msg, &transcription)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error unmarshalling message to transcription:")
|
||||
return
|
||||
}
|
||||
|
||||
var res *models.Transcription
|
||||
// Check if the transcription has a valid ID
|
||||
// If it has ID, it means it's an update
|
||||
if transcription.ID != primitive.NilObjectID {
|
||||
log.Printf("Updating transcription: %v", transcription.ID)
|
||||
// Update transcription in database
|
||||
res, err = s.Db.UpdateTranscription(&transcription)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error updating transcription in database:")
|
||||
return
|
||||
}
|
||||
log.Printf("Updated transcription in database: %v", res)
|
||||
} else {
|
||||
log.Error().Msgf("Transcription not updated, it does not have an ID")
|
||||
return
|
||||
}
|
||||
|
||||
// broadcast with gofiber websocket
|
||||
s.BroadcastTranscription(res)
|
||||
}
|
||||
Reference in New Issue
Block a user