Refactoring: clean architecture project structure

This commit is contained in:
2025-08-09 15:18:42 +03:00
parent 40e207bdb2
commit 8e133630d4
12 changed files with 265 additions and 188 deletions

View File

@@ -0,0 +1,117 @@
package http
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
"git.vakhrushev.me/av/transcriber/internal/entity"
"git.vakhrushev.me/av/transcriber/internal/repo"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type TranscribeHandler struct {
jobRepo repo.TranscriptJobRepository
fileRepo repo.FileRepository
}
func NewTranscribeHandler(jobRepo repo.TranscriptJobRepository, fileRepo repo.FileRepository) *TranscribeHandler {
return &TranscribeHandler{jobRepo: jobRepo, fileRepo: fileRepo}
}
type TranscribeResponse struct {
JobID string `json:"job_id"`
State string `json:"status"`
}
func (h *TranscribeHandler) CreateTranscribeJob(c *gin.Context) {
// Получаем файл из формы
file, header, err := c.Request.FormFile("audio")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No audio file provided"})
return
}
defer file.Close()
// Генерируем UUID для файла
fileId := uuid.New().String()
// Определяем расширение файла
ext := filepath.Ext(header.Filename)
if ext == "" {
ext = ".audio" // fallback если расширение не определено
}
// Создаем путь для сохранения файла
fileName := fmt.Sprintf("%s%s", fileId, ext)
filePath := filepath.Join("data", "files", fileName)
// Создаем файл на диске
dst, err := os.Create(filePath)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create file"})
return
}
defer dst.Close()
// Копируем содержимое загруженного файла
size, err := io.Copy(dst, file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
return
}
// Создаем запись в таблице files
fileRecord := &entity.File{
Id: fileId,
Storage: entity.StorageLocal,
Size: size,
CreatedAt: time.Now(),
}
if err := h.fileRepo.Create(fileRecord); err != nil {
// Удаляем файл если не удалось создать запись в БД
os.Remove(filePath)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file record"})
return
}
// Создаем запись в таблице transcribe_jobs
jobId := uuid.New().String()
job := &entity.TranscribeJob{
Id: jobId,
State: entity.StateCreated,
FileID: &fileId,
IsError: false,
CreatedAt: time.Now(),
}
if err := h.jobRepo.Create(job); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create transcribe job"})
return
}
// Возвращаем успешный ответ
response := TranscribeResponse{
JobID: job.Id,
State: job.State,
}
c.JSON(http.StatusCreated, response)
}
func (h *TranscribeHandler) GetTranscribeJobStatus(c *gin.Context) {
jobID := c.Param("id")
job, err := h.jobRepo.GetByID(jobID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
return
}
c.JSON(http.StatusOK, job)
}

17
internal/entity/file.go Normal file
View File

@@ -0,0 +1,17 @@
package entity
import (
"time"
)
const (
StorageLocal = "local"
StorageS3 = "s3"
)
type File struct {
Id string
Storage string
Size int64
CreatedAt time.Time
}

23
internal/entity/job.go Normal file
View File

@@ -0,0 +1,23 @@
package entity
import (
"time"
)
type TranscribeJob struct {
Id string
State string
FileID *string
IsError bool
ErrorText *string
Worker *string
AcquiredAt *time.Time
CreatedAt time.Time
}
const (
StateCreated = "created"
StateConverted = "converted"
StateUploaded = "uploaded"
StatusFailed = "failed"
)

View File

@@ -0,0 +1,16 @@
package repo
import "git.vakhrushev.me/av/transcriber/internal/entity"
type FileRepository interface {
Create(file *entity.File) error
GetByID(id string) (*entity.File, error)
}
type TranscriptJobRepository interface {
Create(job *entity.TranscribeJob) error
GetByID(id string) (*entity.TranscribeJob, error)
}
type ObjectStorage interface {
}

View File

@@ -0,0 +1,55 @@
package sqlite
import (
"database/sql"
"fmt"
"git.vakhrushev.me/av/transcriber/internal/entity"
"github.com/doug-martin/goqu/v9"
)
type FileRepository struct {
db *sql.DB
gq *goqu.Database
}
func NewFileRepository(conn *sql.DB, gq *goqu.Database) *FileRepository {
return &FileRepository{conn, gq}
}
func (repo *FileRepository) Create(file *entity.File) error {
record := goqu.Record{
"id": file.Id,
"storage": file.Storage,
"size": file.Size,
"created_at": file.CreatedAt,
}
query := repo.gq.Insert("files").Rows(record)
sql, args, err := query.ToSQL()
if err != nil {
return fmt.Errorf("failed to build query: %w", err)
}
_, err = repo.db.Exec(sql, args...)
if err != nil {
return fmt.Errorf("failed to insert file: %w", err)
}
return nil
}
func (repo *FileRepository) GetByID(id string) (*entity.File, error) {
query := repo.gq.From("files").Select("id", "storage", "size", "created_at").Where(goqu.C("id").Eq(id))
sql, args, err := query.ToSQL()
if err != nil {
return nil, fmt.Errorf("failed to build query: %w", err)
}
var file entity.File
err = repo.db.QueryRow(sql, args...).Scan(&file.Id, &file.Storage, &file.Size, &file.CreatedAt)
if err != nil {
return nil, fmt.Errorf("failed to get file: %w", err)
}
return &file, nil
}

View File

@@ -0,0 +1,77 @@
package sqlite
import (
"database/sql"
"fmt"
"git.vakhrushev.me/av/transcriber/internal/entity"
"github.com/doug-martin/goqu/v9"
)
type TranscriptJobRepository struct {
db *sql.DB
gq *goqu.Database
}
func NewTranscriptJobRepository(db *sql.DB, gq *goqu.Database) *TranscriptJobRepository {
return &TranscriptJobRepository{db, gq}
}
func (repo *TranscriptJobRepository) Create(job *entity.TranscribeJob) error {
record := goqu.Record{
"id": job.Id,
"state": job.State,
"file_id": job.FileID,
"is_error": job.IsError,
"error_text": job.ErrorText,
"worker": job.Worker,
"acquired_at": job.AcquiredAt,
"created_at": job.CreatedAt,
}
query := repo.gq.Insert("transcribe_jobs").Rows(record)
sql, args, err := query.ToSQL()
if err != nil {
return fmt.Errorf("failed to build query: %w", err)
}
_, err = repo.db.Exec(sql, args...)
if err != nil {
return fmt.Errorf("failed to insert transcribe job: %w", err)
}
return nil
}
func (repo *TranscriptJobRepository) GetByID(id string) (*entity.TranscribeJob, error) {
query := repo.gq.From("transcribe_jobs").Select(
"id",
"state",
"file_id",
"is_error",
"error_text",
"worker",
"acquired_at",
"created_at",
).Where(goqu.C("id").Eq(id))
sql, args, err := query.ToSQL()
if err != nil {
return nil, fmt.Errorf("failed to build query: %w", err)
}
var job entity.TranscribeJob
err = repo.db.QueryRow(sql, args...).Scan(
&job.Id,
&job.State,
&job.FileID,
&job.IsError,
&job.ErrorText,
&job.Worker,
&job.AcquiredAt,
&job.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to get transcribe job: %w", err)
}
return &job, nil
}