tercul-backend/internal/domain/entities.go
google-labs-jules[bot] b03820de02 Refactor: Improve quality, testing, and core business logic.
This commit introduces a significant refactoring to improve the application's quality, test coverage, and production readiness, focusing on core localization and business logic features.

Key changes include:
- Consolidated the `CreateTranslation` and `UpdateTranslation` commands into a single, more robust `CreateOrUpdateTranslation` command. This uses a database-level `Upsert` for atomicity.
- Centralized authorization for translatable entities into a new `CanEditEntity` check within the application service layer.
- Fixed a critical bug in the `MergeWork` command that caused a UNIQUE constraint violation when merging works with conflicting translations. The logic now intelligently handles language conflicts.
- Implemented decrementing for "like" counts in the analytics service when a like is deleted, ensuring accurate statistics.
- Stabilized the test suite by switching to a file-based database for integration tests, fixing test data isolation issues, and adding a unique index to the `Translation` model to enforce data integrity.
- Refactored manual mocks to use the `testify/mock` library for better consistency and maintainability.
2025-10-05 09:41:40 +00:00

1028 lines
33 KiB
Go

package domain
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"time"
)
// JSONB is a custom type for JSONB columns.
type JSONB map[string]interface{}
// Value marshals JSONB for storing in the DB.
func (j JSONB) Value() (driver.Value, error) {
if j == nil {
return "{}", nil
}
return json.Marshal(j)
}
// Scan unmarshals a JSONB value.
func (j *JSONB) Scan(value interface{}) error {
if value == nil {
*j = JSONB{}
return nil
}
switch v := value.(type) {
case []byte:
if len(v) == 0 {
*j = JSONB{}
return nil
}
return json.Unmarshal(v, j)
case string:
if v == "" {
*j = JSONB{}
return nil
}
return json.Unmarshal([]byte(v), j)
default:
return fmt.Errorf("failed to unmarshal JSONB value of type %T: %v", value, value)
}
}
// BaseModel contains common fields for all models
type BaseModel struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
}
// TranslatableModel extends BaseModel with language support
type TranslatableModel struct {
BaseModel
Language string `gorm:"size:50;default:'multi'"`
Slug string `gorm:"size:255;index"`
}
// Translation status enum
type TranslationStatus string
const (
TranslationStatusDraft TranslationStatus = "draft"
TranslationStatusPublished TranslationStatus = "published"
TranslationStatusReviewing TranslationStatus = "reviewing"
TranslationStatusRejected TranslationStatus = "rejected"
)
// UserRole enum
type UserRole string
const (
UserRoleReader UserRole = "reader"
UserRoleContributor UserRole = "contributor"
UserRoleReviewer UserRole = "reviewer"
UserRoleEditor UserRole = "editor"
UserRoleAdmin UserRole = "admin"
)
// User represents a user of the platform
type User struct {
BaseModel
Username string `gorm:"size:50;not null;unique"`
Email string `gorm:"size:100;not null;unique"`
Password string `gorm:"size:255;not null"`
FirstName string `gorm:"size:50"`
LastName string `gorm:"size:50"`
DisplayName string `gorm:"size:100"`
Bio string `gorm:"type:text"`
AvatarURL string `gorm:"size:255"`
Role UserRole `gorm:"size:20;default:'reader'"`
LastLoginAt *time.Time
Verified bool `gorm:"default:false"`
Active bool `gorm:"default:true"`
Translations []*Translation `gorm:"foreignKey:TranslatorID"`
Comments []*Comment `gorm:"foreignKey:UserID"`
Likes []*Like `gorm:"foreignKey:UserID"`
Bookmarks []*Bookmark `gorm:"foreignKey:UserID"`
Collections []*Collection `gorm:"foreignKey:UserID"`
Contributions []*Contribution `gorm:"foreignKey:UserID"`
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
CityID *uint
City *City `gorm:"foreignKey:CityID"`
AddressID *uint
Address *Address `gorm:"foreignKey:AddressID"`
}
// UserProfile represents additional profile information for a user
type UserProfile struct {
BaseModel
UserID uint `gorm:"uniqueIndex"`
User *User `gorm:"foreignKey:UserID"`
PhoneNumber string `gorm:"size:20"`
Website string `gorm:"size:255"`
Twitter string `gorm:"size:50"`
Facebook string `gorm:"size:50"`
LinkedIn string `gorm:"size:50"`
Github string `gorm:"size:50"`
Preferences JSONB `gorm:"type:jsonb;default:'{}'"`
Settings JSONB `gorm:"type:jsonb;default:'{}'"`
}
// UserSession represents a user session
type UserSession struct {
BaseModel
UserID uint `gorm:"index"`
User *User `gorm:"foreignKey:UserID"`
Token string `gorm:"size:255;not null;uniqueIndex"`
IP string `gorm:"size:50"`
UserAgent string `gorm:"size:255"`
ExpiresAt time.Time `gorm:"not null"`
}
// PasswordReset represents a password reset request
type PasswordReset struct {
BaseModel
UserID uint `gorm:"index"`
User *User `gorm:"foreignKey:UserID"`
Token string `gorm:"size:255;not null;uniqueIndex"`
ExpiresAt time.Time `gorm:"not null"`
Used bool `gorm:"default:false"`
}
// EmailVerification represents an email verification request
type EmailVerification struct {
BaseModel
UserID uint `gorm:"index"`
User *User `gorm:"foreignKey:UserID"`
Token string `gorm:"size:255;not null;uniqueIndex"`
ExpiresAt time.Time `gorm:"not null"`
Used bool `gorm:"default:false"`
}
func (u *User) SetPassword(password string) error {
if password == "" {
return errors.New("password cannot be empty")
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return errors.New("failed to hash password: " + err.Error())
}
u.Password = string(hashedPassword)
return nil
}
func (u *User) BeforeSave(tx *gorm.DB) error {
if u.Password == "" {
return nil
}
if len(u.Password) >= 60 && u.Password[:4] == "$2a$" {
return nil
}
return u.SetPassword(u.Password)
}
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
return err == nil
}
type AuthorStatus string
const (
AuthorStatusActive AuthorStatus = "active"
AuthorStatusInactive AuthorStatus = "inactive"
AuthorStatusDeceased AuthorStatus = "deceased"
)
type Author struct {
TranslatableModel
Name string `gorm:"size:255;not null"`
Status AuthorStatus `gorm:"size:50;default:'active'"`
BirthDate *time.Time
DeathDate *time.Time
OpenLibraryID string `gorm:"size:50;index"`
Books []*Book `gorm:"many2many:book_authors"`
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
CityID *uint
City *City `gorm:"foreignKey:CityID"`
PlaceID *uint
Place *Place `gorm:"foreignKey:PlaceID"`
AddressID *uint
Address *Address `gorm:"foreignKey:AddressID"`
Translations []*Translation `gorm:"polymorphic:Translatable"`
Copyrights []*Copyright `gorm:"many2many:author_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:author_monetizations;constraint:OnDelete:CASCADE"`
}
type BookStatus string
const (
BookStatusDraft BookStatus = "draft"
BookStatusPublished BookStatus = "published"
BookStatusOutOfPrint BookStatus = "out_of_print"
BookStatusArchived BookStatus = "archived"
)
type BookFormat string
const (
BookFormatHardcover BookFormat = "hardcover"
BookFormatPaperback BookFormat = "paperback"
BookFormatEbook BookFormat = "ebook"
BookFormatAudiobook BookFormat = "audiobook"
BookFormatDigital BookFormat = "digital"
)
type Book struct {
TranslatableModel
Title string `gorm:"size:255;not null"`
Description string `gorm:"type:text"`
ISBN string `gorm:"size:20;index"`
Format BookFormat `gorm:"size:50;default:'paperback'"`
Status BookStatus `gorm:"size:50;default:'draft'"`
PublishedAt *time.Time
Authors []*Author `gorm:"many2many:book_authors"`
PublisherID *uint
Publisher *Publisher `gorm:"foreignKey:PublisherID"`
Translations []*Translation `gorm:"polymorphic:Translatable"`
Copyrights []*Copyright `gorm:"many2many:book_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:book_monetizations;constraint:OnDelete:CASCADE"`
}
type PublisherStatus string
const (
PublisherStatusActive PublisherStatus = "active"
PublisherStatusInactive PublisherStatus = "inactive"
PublisherStatusDefunct PublisherStatus = "defunct"
)
type Publisher struct {
TranslatableModel
Name string `gorm:"size:255;not null"`
Description string `gorm:"type:text"`
Status PublisherStatus `gorm:"size:50;default:'active'"`
Books []*Book `gorm:"foreignKey:PublisherID"`
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
Translations []*Translation `gorm:"polymorphic:Translatable"`
Copyrights []*Copyright `gorm:"many2many:publisher_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:publisher_monetizations;constraint:OnDelete:CASCADE"`
}
type SourceStatus string
const (
SourceStatusActive SourceStatus = "active"
SourceStatusInactive SourceStatus = "inactive"
SourceStatusArchived SourceStatus = "archived"
)
type Source struct {
TranslatableModel
Name string `gorm:"size:255;not null"`
Description string `gorm:"type:text"`
URL string `gorm:"size:512"`
Status SourceStatus `gorm:"size:50;default:'active'"`
Translations []*Translation `gorm:"polymorphic:Translatable"`
Copyrights []*Copyright `gorm:"many2many:source_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:source_monetizations;constraint:OnDelete:CASCADE"`
}
type EditionStatus string
const (
EditionStatusDraft EditionStatus = "draft"
EditionStatusPublished EditionStatus = "published"
EditionStatusOutOfPrint EditionStatus = "out_of_print"
EditionStatusArchived EditionStatus = "archived"
)
type Edition struct {
BaseModel
Title string `gorm:"size:255;not null"`
Description string `gorm:"type:text"`
ISBN string `gorm:"size:20;index"`
Version string `gorm:"size:50"`
Format BookFormat `gorm:"size:50;default:'paperback'"`
Status EditionStatus `gorm:"size:50;default:'draft'"`
PublishedAt *time.Time
BookID uint
Book *Book `gorm:"foreignKey:BookID"`
}
func (a *Author) BeforeSave(tx *gorm.DB) error {
if a.Name == "" {
a.Name = "Unknown Author"
}
return nil
}
func (b *Book) BeforeSave(tx *gorm.DB) error {
if b.Title == "" {
b.Title = "Untitled Book"
}
return nil
}
func (p *Publisher) BeforeSave(tx *gorm.DB) error {
if p.Name == "" {
p.Name = "Unknown Publisher"
}
return nil
}
type Comment struct {
BaseModel
Text string `gorm:"type:text;not null"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
LineNumber *int `gorm:"index"`
TextBlockID *uint
TextBlock *TextBlock `gorm:"foreignKey:TextBlockID"`
ParentID *uint
Parent *Comment `gorm:"foreignKey:ParentID"`
Children []*Comment `gorm:"foreignKey:ParentID"`
Likes []*Like `gorm:"foreignKey:CommentID"`
}
type Like struct {
BaseModel
UserID uint `gorm:"index;uniqueIndex:uniq_like_user_target"`
User *User `gorm:"foreignKey:UserID"`
WorkID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"`
TranslationID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"`
Translation *Translation `gorm:"foreignKey:TranslationID"`
CommentID *uint `gorm:"index;uniqueIndex:uniq_like_user_target"`
Comment *Comment `gorm:"foreignKey:CommentID"`
}
type Bookmark struct {
BaseModel
Name string `gorm:"size:100"`
UserID uint `gorm:"index;uniqueIndex:uniq_bookmark_user_work"`
User *User `gorm:"foreignKey:UserID"`
WorkID uint `gorm:"index;uniqueIndex:uniq_bookmark_user_work"`
Notes string `gorm:"type:text"`
LastReadAt *time.Time
Progress int `gorm:"default:0"`
}
type Collection struct {
TranslatableModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
IsPublic bool `gorm:"default:true"`
CoverImageURL string `gorm:"size:255"`
}
type CollectionWork struct {
CollectionID uint `gorm:"primaryKey"`
WorkID uint `gorm:"primaryKey"`
}
type Contribution struct {
BaseModel
Name string `gorm:"size:100;not null"`
Status string `gorm:"size:20;default:'draft'"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
ReviewerID *uint
Reviewer *User `gorm:"foreignKey:ReviewerID"`
ReviewedAt *time.Time
Feedback string `gorm:"type:text"`
}
type Country struct {
TranslatableModel
Name string `gorm:"size:100;not null"`
Code string `gorm:"size:2;not null;uniqueIndex"`
PhoneCode string `gorm:"size:10"`
Currency string `gorm:"size:3"`
Continent string `gorm:"size:20"`
Cities []*City `gorm:"foreignKey:CountryID"`
Places []*Place `gorm:"foreignKey:CountryID"`
Addresses []*Address `gorm:"foreignKey:CountryID"`
}
type Language struct {
BaseModel
Code string `gorm:"size:16;not null;uniqueIndex"`
Name string `gorm:"size:100;not null"`
Script string `gorm:"size:20"`
Direction string `gorm:"size:5"`
}
type City struct {
TranslatableModel
Name string `gorm:"size:100;not null"`
CountryID uint
Country *Country `gorm:"foreignKey:CountryID"`
Places []*Place `gorm:"foreignKey:CityID"`
Addresses []*Address `gorm:"foreignKey:CityID"`
}
type Place struct {
TranslatableModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Latitude float64
Longitude float64
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
CityID *uint
City *City `gorm:"foreignKey:CityID"`
}
type Address struct {
BaseModel
Street string `gorm:"size:255"`
StreetNumber string `gorm:"size:20"`
PostalCode string `gorm:"size:20"`
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
CityID *uint
City *City `gorm:"foreignKey:CityID"`
Latitude *float64
Longitude *float64
}
type Tag struct {
BaseModel
Name string `gorm:"size:100;not null;uniqueIndex"`
Description string `gorm:"type:text"`
Slug string `gorm:"size:255;index"`
}
type Category struct {
BaseModel
Name string `gorm:"size:100;not null;uniqueIndex"`
Description string `gorm:"type:text"`
ParentID *uint
Parent *Category `gorm:"foreignKey:ParentID"`
Children []*Category `gorm:"foreignKey:ParentID"`
Path string `gorm:"size:1024;index"`
Slug string `gorm:"size:255;index"`
}
type Series struct {
BaseModel
Name string `gorm:"size:255;not null;uniqueIndex"`
Description string `gorm:"type:text"`
}
type Translation struct {
BaseModel
Title string `gorm:"size:255;not null"`
Content string `gorm:"type:text"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null;uniqueIndex:idx_translation_target_language"`
Status TranslationStatus `gorm:"size:50;default:'draft'"`
PublishedAt *time.Time
TranslatableID uint `gorm:"not null;uniqueIndex:idx_translation_target_language"`
TranslatableType string `gorm:"size:50;not null;uniqueIndex:idx_translation_target_language"`
TranslatorID *uint
Translator *User `gorm:"foreignKey:TranslatorID"`
IsOriginalLanguage bool `gorm:"default:false"`
AudioURL string `gorm:"size:512"`
DateTranslated *time.Time
}
type TranslationField struct {
BaseModel
TranslationID uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
FieldName string `gorm:"size:100;not null"`
FieldValue string `gorm:"type:text;not null"`
Language string `gorm:"size:50;not null"`
}
type TranslatableEntity interface {
GetID() uint
GetType() string
GetDefaultLanguage() string
}
func GetTranslatableFields(entityType string) []string {
fieldMappings := map[string][]string{
"Work": {"title", "content", "description"},
"Author": {"name", "biography"},
"Book": {"title", "description"},
"Country": {"name"},
"Publisher": {"name", "description"},
"Source": {"name", "description"},
}
if fields, exists := fieldMappings[entityType]; exists {
return fields
}
return []string{}
}
func (t *Translation) BeforeSave(tx *gorm.DB) error {
if t.Title == "" {
t.Title = "Untitled Translation"
}
return nil
}
func (a *Author) GetID() uint { return a.ID }
func (a *Author) GetType() string { return "Author" }
func (a *Author) GetDefaultLanguage() string { return a.Language }
func (b *Book) GetID() uint { return b.ID }
func (b *Book) GetType() string { return "Book" }
func (b *Book) GetDefaultLanguage() string { return b.Language }
func (c *Country) GetID() uint { return c.ID }
func (c *Country) GetType() string { return "Country" }
func (c *Country) GetDefaultLanguage() string { return c.Language }
func (p *Publisher) GetID() uint { return p.ID }
func (p *Publisher) GetType() string { return "Publisher" }
func (p *Publisher) GetDefaultLanguage() string { return p.Language }
func (s *Source) GetID() uint { return s.ID }
func (s *Source) GetType() string { return "Source" }
func (s *Source) GetDefaultLanguage() string { return s.Language }
type Copyright struct {
BaseModel
Identificator string `gorm:"size:100;not null"`
Name string `gorm:"size:255;not null"`
Description string `gorm:"type:text"`
License string `gorm:"size:100"`
StartDate *time.Time
EndDate *time.Time
Translations []CopyrightTranslation `gorm:"foreignKey:CopyrightID"`
}
type AuthorCopyright struct {
AuthorID uint `gorm:"primaryKey;index"`
CopyrightID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (AuthorCopyright) TableName() string { return "author_copyrights" }
type BookCopyright struct {
BookID uint `gorm:"primaryKey;index"`
CopyrightID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (BookCopyright) TableName() string { return "book_copyrights" }
type PublisherCopyright struct {
PublisherID uint `gorm:"primaryKey;index"`
CopyrightID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (PublisherCopyright) TableName() string { return "publisher_copyrights" }
type SourceCopyright struct {
SourceID uint `gorm:"primaryKey;index"`
CopyrightID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (SourceCopyright) TableName() string { return "source_copyrights" }
type CopyrightTranslation struct {
BaseModel
CopyrightID uint
Copyright *Copyright `gorm:"foreignKey:CopyrightID"`
LanguageCode string `gorm:"size:10;not null"`
Message string `gorm:"type:text;not null"`
Description string `gorm:"type:text"`
}
type CopyrightClaimStatus string
const (
CopyrightClaimStatusPending CopyrightClaimStatus = "pending"
CopyrightClaimStatusApproved CopyrightClaimStatus = "approved"
CopyrightClaimStatusRejected CopyrightClaimStatus = "rejected"
)
type CopyrightClaim struct {
BaseModel
Details string `gorm:"type:text;not null"`
Status CopyrightClaimStatus `gorm:"size:50;default:'pending'"`
ClaimDate time.Time `gorm:"not null"`
Resolution string `gorm:"type:text"`
ResolvedAt *time.Time
UserID *uint
User *User `gorm:"foreignKey:UserID"`
}
type MonetizationType string
const (
MonetizationTypeSubscription MonetizationType = "subscription"
MonetizationTypeOneTime MonetizationType = "one_time"
MonetizationTypeDonation MonetizationType = "donation"
MonetizationTypeAdvertisement MonetizationType = "advertisement"
MonetizationTypeLicensing MonetizationType = "licensing"
)
type MonetizationStatus string
const (
MonetizationStatusActive MonetizationStatus = "active"
MonetizationStatusInactive MonetizationStatus = "inactive"
MonetizationStatusPending MonetizationStatus = "pending"
)
type AuthorMonetization struct {
AuthorID uint `gorm:"primaryKey;index"`
MonetizationID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (AuthorMonetization) TableName() string { return "author_monetizations" }
type BookMonetization struct {
BookID uint `gorm:"primaryKey;index"`
MonetizationID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (BookMonetization) TableName() string { return "book_monetizations" }
type PublisherMonetization struct {
PublisherID uint `gorm:"primaryKey;index"`
MonetizationID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (PublisherMonetization) TableName() string { return "publisher_monetizations" }
type SourceMonetization struct {
SourceID uint `gorm:"primaryKey;index"`
MonetizationID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (SourceMonetization) TableName() string { return "source_monetizations" }
type Monetization struct {
BaseModel
Amount float64 `gorm:"type:decimal(10,2);default:0.0"`
Currency string `gorm:"size:3;default:'USD'"`
Type MonetizationType `gorm:"size:50"`
Status MonetizationStatus `gorm:"size:50;default:'active'"`
StartDate *time.Time
EndDate *time.Time
Language string `gorm:"size:50;not null"`
}
type License struct {
BaseModel
SPDXIdentifier string `gorm:"size:64;uniqueIndex"`
Name string `gorm:"size:255;not null"`
URL string `gorm:"size:512"`
Description string `gorm:"type:text"`
}
type ModerationFlag struct {
BaseModel
TargetType string `gorm:"size:50;not null"`
TargetID uint `gorm:"not null"`
Reason string `gorm:"size:255"`
Status string `gorm:"size:50;default:'open'"`
ReviewerID *uint
Reviewer *User `gorm:"foreignKey:ReviewerID"`
Notes string `gorm:"type:text"`
}
type AuditLog struct {
BaseModel
ActorID *uint
Actor *User `gorm:"foreignKey:ActorID"`
Action string `gorm:"size:50;not null"`
EntityType string `gorm:"size:50;not null"`
EntityID uint `gorm:"not null"`
Before JSONB `gorm:"type:jsonb;default:'{}'"`
After JSONB `gorm:"type:jsonb;default:'{}'"`
At time.Time `gorm:"autoCreateTime"`
}
// And all other models from the files I read...
// This is getting very long, but it's the correct approach.
// I will just paste the rest of the structs here.
type TranslationStats struct {
BaseModel
Views int64 `gorm:"default:0"`
Likes int64 `gorm:"default:0"`
Comments int64 `gorm:"default:0"`
Shares int64 `gorm:"default:0"`
ReadingTime int `gorm:"default:0"`
Sentiment float64 `gorm:"type:decimal(5,2);default:0.0"`
TranslationID uint `gorm:"uniqueIndex;index"`
Translation *Translation `gorm:"foreignKey:TranslationID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type UserEngagement struct {
BaseModel
UserID uint `gorm:"index;uniqueIndex:uniq_user_engagement_date"`
User *User `gorm:"foreignKey:UserID"`
Date time.Time `gorm:"type:date;uniqueIndex:uniq_user_engagement_date"`
WorksRead int `gorm:"default:0"`
CommentsMade int `gorm:"default:0"`
LikesGiven int `gorm:"default:0"`
BookmarksMade int `gorm:"default:0"`
TranslationsMade int `gorm:"default:0"`
}
type Trending struct {
BaseModel
EntityType string `gorm:"size:50;not null;index:idx_trending_entity_period_date,uniqueIndex:uniq_trending_rank"`
EntityID uint `gorm:"not null;index:idx_trending_entity_period_date,uniqueIndex:uniq_trending_rank"`
Rank int `gorm:"not null;uniqueIndex:uniq_trending_rank"`
Score float64 `gorm:"type:decimal(10,2);default:0.0"`
TimePeriod string `gorm:"size:50;not null;index:idx_trending_entity_period_date,uniqueIndex:uniq_trending_rank"`
Date time.Time `gorm:"type:date;index:idx_trending_entity_period_date,uniqueIndex:uniq_trending_rank"`
}
type BookStats struct {
BaseModel
Sales int64 `gorm:"default:0"`
Views int64 `gorm:"default:0"`
Likes int64 `gorm:"default:0"`
BookID uint `gorm:"uniqueIndex;index"`
Book *Book `gorm:"foreignKey:BookID"`
}
type CollectionStats struct {
BaseModel
Items int64 `gorm:"default:0"`
Views int64 `gorm:"default:0"`
Likes int64 `gorm:"default:0"`
CollectionID uint `gorm:"uniqueIndex;index"`
Collection *Collection `gorm:"foreignKey:CollectionID"`
}
type MediaStats struct {
BaseModel
Views int64 `gorm:"default:0"`
Downloads int64 `gorm:"default:0"`
Shares int64 `gorm:"default:0"`
MediaID uint `gorm:"uniqueIndex;index"`
Media interface{} `gorm:"-"`
}
type AuthorCountry struct {
BaseModel
AuthorID uint `gorm:"index;uniqueIndex:uniq_author_country"`
Author *Author `gorm:"foreignKey:AuthorID"`
CountryID uint `gorm:"index;uniqueIndex:uniq_author_country"`
Country *Country `gorm:"foreignKey:CountryID"`
}
type BookAuthor struct {
BaseModel
BookID uint `gorm:"index;uniqueIndex:uniq_book_author_role"`
Book *Book `gorm:"foreignKey:BookID"`
AuthorID uint `gorm:"index;uniqueIndex:uniq_book_author_role"`
Author *Author `gorm:"foreignKey:AuthorID"`
Role string `gorm:"size:50;default:'author';uniqueIndex:uniq_book_author_role"`
Ordinal int `gorm:"default:0"`
}
type ReadabilityScore struct {
BaseModel
Score float64 `gorm:"type:decimal(5,2)"`
Language string `gorm:"size:50;not null"`
Method string `gorm:"size:50"`
WorkID uint
}
type WritingStyle struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
WorkID uint
}
type LinguisticLayer struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
Type string `gorm:"size:50"`
WorkID uint
Data JSONB `gorm:"type:jsonb;default:'{}'"`
}
type TextBlock struct {
BaseModel
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
Index int `gorm:"index"`
Type string `gorm:"size:30"`
StartOffset int `gorm:"default:0"`
EndOffset int `gorm:"default:0"`
Text string `gorm:"type:text"`
}
type TextMetadata struct {
BaseModel
Analysis string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
WordCount int `gorm:"default:0"`
SentenceCount int `gorm:"default:0"`
ParagraphCount int `gorm:"default:0"`
AverageWordLength float64 `gorm:"type:decimal(5,2)"`
AverageSentenceLength float64 `gorm:"type:decimal(5,2)"`
WorkID uint
}
type PoeticAnalysis struct {
BaseModel
Structure string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
RhymeScheme string `gorm:"size:100"`
MeterType string `gorm:"size:50"`
StanzaCount int `gorm:"default:0"`
LineCount int `gorm:"default:0"`
WorkID uint
}
type Word struct {
BaseModel
Text string `gorm:"size:100;not null"`
Language string `gorm:"size:50;not null"`
PartOfSpeech string `gorm:"size:20"`
Lemma string `gorm:"size:100"`
ConceptID *uint
Concept *Concept `gorm:"foreignKey:ConceptID"`
}
type WordOccurrence struct {
BaseModel
TextBlockID uint
TextBlock *TextBlock `gorm:"foreignKey:TextBlockID"`
WordID *uint
Word *Word `gorm:"foreignKey:WordID"`
StartOffset int `gorm:"default:0"`
EndOffset int `gorm:"default:0"`
Lemma string `gorm:"size:100"`
PartOfSpeech string `gorm:"size:20"`
}
type Concept struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Words []*Word `gorm:"foreignKey:ConceptID"`
}
type LanguageEntity struct {
BaseModel
Name string `gorm:"size:100;not null"`
Type string `gorm:"size:50"`
Language string `gorm:"size:50;not null"`
}
type EntityOccurrence struct {
BaseModel
TextBlockID uint
TextBlock *TextBlock `gorm:"foreignKey:TextBlockID"`
LanguageEntityID uint
LanguageEntity *LanguageEntity `gorm:"foreignKey:LanguageEntityID"`
StartOffset int `gorm:"default:0"`
EndOffset int `gorm:"default:0"`
}
type LanguageAnalysis struct {
BaseModel
Language string `gorm:"size:50;not null;uniqueIndex:uniq_work_language_analysis"`
Analysis JSONB `gorm:"type:jsonb;default:'{}'"`
WorkID uint `gorm:"index;uniqueIndex:uniq_work_language_analysis"`
}
type Gamification struct {
BaseModel
Points int `gorm:"default:0"`
Level int `gorm:"default:1"`
Badges JSONB `gorm:"type:jsonb;default:'{}'"`
Streaks int `gorm:"default:0"`
LastActive *time.Time
UserID uint `gorm:"uniqueIndex;index"`
User *User `gorm:"foreignKey:UserID"`
}
type Stats struct {
BaseModel
Data JSONB `gorm:"type:jsonb;default:'{}'"`
Period string `gorm:"size:50"`
StartDate time.Time
EndDate time.Time
UserID *uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
}
type SearchDocument struct {
BaseModel
EntityType string `gorm:"size:50;index"`
EntityID uint `gorm:"index"`
LanguageCode string `gorm:"size:16;index"`
Title string `gorm:"size:512"`
Body string `gorm:"type:text"`
Keywords string `gorm:"type:text"`
}
type Emotion struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
Intensity float64 `gorm:"type:decimal(5,2);default:0.0"`
UserID *uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
CollectionID *uint
Collection *Collection `gorm:"foreignKey:CollectionID"`
}
type Mood struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
}
type TopicCluster struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Keywords string `gorm:"type:text"`
}
type Edge struct {
BaseModel
SourceTable string `gorm:"size:50;not null;index:idx_edge_source;uniqueIndex:uniq_edge"`
SourceID uint `gorm:"not null;index:idx_edge_source;uniqueIndex:uniq_edge"`
TargetTable string `gorm:"size:50;not null;index:idx_edge_target;uniqueIndex:uniq_edge"`
TargetID uint `gorm:"not null;index:idx_edge_target;uniqueIndex:uniq_edge"`
Relation string `gorm:"size:50;default:'ASSOCIATED_WITH';not null;index;uniqueIndex:uniq_edge"`
Language string `gorm:"size:10;default:'en';index;uniqueIndex:uniq_edge"`
Extra JSONB `gorm:"type:jsonb;default:'{}'"`
}
type Embedding struct {
BaseModel
ExternalID string `gorm:"size:64;index"`
EntityType string `gorm:"size:50;not null;index:idx_embedding_entity;uniqueIndex:uniq_embedding"`
EntityID uint `gorm:"not null;index:idx_embedding_entity;uniqueIndex:uniq_embedding"`
Model string `gorm:"size:50;not null;uniqueIndex:uniq_embedding"`
Dim int `gorm:"default:0"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
}
type Localization struct {
BaseModel
Key string `gorm:"size:255;not null;uniqueIndex:uniq_localization_key_language"`
Value string `gorm:"type:text;not null"`
Language string `gorm:"size:50;not null;uniqueIndex:uniq_localization_key_language"`
}
type Media struct {
BaseModel
URL string `gorm:"size:512;not null"`
Type string `gorm:"size:50;not null"`
MimeType string `gorm:"size:100"`
Size int64 `gorm:"default:0"`
Title string `gorm:"size:255"`
Description string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
AuthorID *uint
Author *Author `gorm:"foreignKey:AuthorID"`
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
CityID *uint
City *City `gorm:"foreignKey:CityID"`
}
type Notification struct {
BaseModel
Message string `gorm:"type:text;not null"`
Type string `gorm:"size:50"`
Read bool `gorm:"default:false"`
Language string `gorm:"size:50;not null"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
RelatedID *uint
RelatedType string `gorm:"size:50"`
}
type EditorialWorkflow struct {
BaseModel
Stage string `gorm:"size:50;not null"`
Notes string `gorm:"type:text"`
Language string `gorm:"size:50;not null"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
AssignedToID *uint
AssignedTo *User `gorm:"foreignKey:AssignedToID"`
DueDate *time.Time
CompletedAt *time.Time
}
type Admin struct {
BaseModel
UserID uint
User *User `gorm:"foreignKey:UserID"`
Role string `gorm:"size:50;not null"`
Permissions JSONB `gorm:"type:jsonb;default:'{}'"`
}
type Vote struct {
BaseModel
Value int `gorm:"default:0"`
UserID uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
CommentID *uint
Comment *Comment `gorm:"foreignKey:CommentID"`
}
type Contributor struct {
BaseModel
Name string `gorm:"size:100;not null"`
Role string `gorm:"size:50"`
UserID *uint
User *User `gorm:"foreignKey:UserID"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
}
type InteractionEvent struct {
BaseModel
UserID *uint
User *User `gorm:"foreignKey:UserID"`
TargetType string `gorm:"size:50;not null"`
TargetID uint `gorm:"not null"`
Kind string `gorm:"size:30;not null"`
OccurredAt time.Time `gorm:"index"`
}
type HybridEntityWork struct {
BaseModel
Name string `gorm:"size:100;not null"`
Type string `gorm:"size:50"`
WorkID *uint
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
}