tercul-backend/internal/domain/work/entity.go
google-labs-jules[bot] a1c8088987 feat: Implement MergeWork command and consolidate tasks
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.
2025-10-05 00:24:23 +00:00

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" }