mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 02:51:34 +00:00
This commit introduces the `MergeWork` command, a new feature to merge two `Work` entities, their associations, and their statistics. The entire operation is performed atomically within a database transaction to ensure data integrity. Key changes include: - A new `MergeWork` method in `internal/app/work/commands.go`. - An `Add` method on the `WorkStats` entity for combining statistics. - A new `GetWithAssociationsInTx` method on the `WorkRepository` to fetch entities within a transaction. - A comprehensive integration test using an in-memory SQLite database to validate the merge logic. This commit also consolidates all scattered TODOs and build issues from `TODO.md` and `BUILD_ISSUES.md` into a single, actionable `TASKS.md` file. The legacy documentation files have been removed to create a single source of truth for pending work.
132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
package work
|
|
|
|
import (
|
|
"gorm.io/gorm"
|
|
"tercul/internal/domain"
|
|
"time"
|
|
)
|
|
|
|
type WorkStatus string
|
|
|
|
const (
|
|
WorkStatusDraft WorkStatus = "draft"
|
|
WorkStatusPublished WorkStatus = "published"
|
|
WorkStatusArchived WorkStatus = "archived"
|
|
WorkStatusDeleted WorkStatus = "deleted"
|
|
)
|
|
|
|
type WorkType string
|
|
|
|
const (
|
|
WorkTypePoetry WorkType = "poetry"
|
|
WorkTypeProse WorkType = "prose"
|
|
WorkTypeDrama WorkType = "drama"
|
|
WorkTypeEssay WorkType = "essay"
|
|
WorkTypeNovel WorkType = "novel"
|
|
WorkTypeShortStory WorkType = "short_story"
|
|
WorkTypeNovella WorkType = "novella"
|
|
WorkTypePlay WorkType = "play"
|
|
WorkTypeScript WorkType = "script"
|
|
WorkTypeOther WorkType = "other"
|
|
)
|
|
|
|
type Work struct {
|
|
domain.TranslatableModel
|
|
Title string `gorm:"size:255;not null"`
|
|
Description string `gorm:"type:text"`
|
|
Type WorkType `gorm:"size:50;default:'other'"`
|
|
Status WorkStatus `gorm:"size:50;default:'draft'"`
|
|
PublishedAt *time.Time
|
|
Translations []*domain.Translation `gorm:"polymorphic:Translatable"`
|
|
Authors []*domain.Author `gorm:"many2many:work_authors"`
|
|
Tags []*domain.Tag `gorm:"many2many:work_tags"`
|
|
Categories []*domain.Category `gorm:"many2many:work_categories"`
|
|
Copyrights []*domain.Copyright `gorm:"many2many:work_copyrights;constraint:OnDelete:CASCADE"`
|
|
Monetizations []*domain.Monetization `gorm:"many2many:work_monetizations;constraint:OnDelete:CASCADE"`
|
|
}
|
|
|
|
func (w *Work) BeforeSave(tx *gorm.DB) error {
|
|
if w.Title == "" {
|
|
w.Title = "Untitled Work"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *Work) GetID() uint { return w.ID }
|
|
func (w *Work) GetType() string { return "Work" }
|
|
func (w *Work) GetDefaultLanguage() string { return w.Language }
|
|
|
|
type WorkStats struct {
|
|
domain.BaseModel
|
|
Views int64 `gorm:"default:0"`
|
|
Likes int64 `gorm:"default:0"`
|
|
Comments int64 `gorm:"default:0"`
|
|
Bookmarks int64 `gorm:"default:0"`
|
|
Shares int64 `gorm:"default:0"`
|
|
TranslationCount int64 `gorm:"default:0"`
|
|
ReadingTime int `gorm:"default:0"`
|
|
Complexity float64 `gorm:"type:decimal(5,2);default:0.0"`
|
|
Sentiment float64 `gorm:"type:decimal(5,2);default:0.0"`
|
|
WorkID uint `gorm:"uniqueIndex;index"`
|
|
Work *Work `gorm:"foreignKey:WorkID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
|
}
|
|
|
|
// Add combines the values of another WorkStats into this one.
|
|
func (ws *WorkStats) Add(other *WorkStats) {
|
|
if other == nil {
|
|
return
|
|
}
|
|
ws.Views += other.Views
|
|
ws.Likes += other.Likes
|
|
ws.Comments += other.Comments
|
|
ws.Bookmarks += other.Bookmarks
|
|
ws.Shares += other.Shares
|
|
ws.TranslationCount += other.TranslationCount
|
|
ws.ReadingTime += other.ReadingTime
|
|
// Note: Complexity and Sentiment are not additive. We could average them,
|
|
// but for now, we'll just keep the target's values.
|
|
}
|
|
|
|
type WorkSeries struct {
|
|
domain.BaseModel
|
|
WorkID uint `gorm:"index;uniqueIndex:uniq_work_series"`
|
|
Work *Work `gorm:"foreignKey:WorkID"`
|
|
SeriesID uint `gorm:"index;uniqueIndex:uniq_work_series"`
|
|
Series *domain.Series `gorm:"foreignKey:SeriesID"`
|
|
NumberInSeries int `gorm:"default:0"`
|
|
}
|
|
|
|
type BookWork struct {
|
|
domain.BaseModel
|
|
BookID uint `gorm:"index;uniqueIndex:uniq_book_work"`
|
|
Book *domain.Book `gorm:"foreignKey:BookID"`
|
|
WorkID uint `gorm:"index;uniqueIndex:uniq_book_work"`
|
|
Work *Work `gorm:"foreignKey:WorkID"`
|
|
Order int `gorm:"default:0"`
|
|
}
|
|
|
|
type WorkAuthor struct {
|
|
domain.BaseModel
|
|
WorkID uint `gorm:"index;uniqueIndex:uniq_work_author_role"`
|
|
Work *Work `gorm:"foreignKey:WorkID"`
|
|
AuthorID uint `gorm:"index;uniqueIndex:uniq_work_author_role"`
|
|
Author *domain.Author `gorm:"foreignKey:AuthorID"`
|
|
Role string `gorm:"size:50;default:'author';uniqueIndex:uniq_work_author_role"`
|
|
Ordinal int `gorm:"default:0"`
|
|
}
|
|
|
|
type WorkCopyright struct {
|
|
WorkID uint `gorm:"primaryKey;index"`
|
|
CopyrightID uint `gorm:"primaryKey;index"`
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
func (WorkCopyright) TableName() string { return "work_copyrights" }
|
|
|
|
type WorkMonetization struct {
|
|
WorkID uint `gorm:"primaryKey;index"`
|
|
MonetizationID uint `gorm:"primaryKey;index"`
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
func (WorkMonetization) TableName() string { return "work_monetizations" } |