tercul-backend/internal/domain/entities.go
google-labs-jules[bot] 042773c8f9 This commit addresses the "Stabilize non-linguistics tests and interfaces" task from TODO.md.
The main changes are:
-   Fixed GORM migration issues related to polymorphic many-to-many relationships by using the `gorm:"-"` tag on the `Copyrights`, `Monetizations`, and `Claimables` fields in the domain entities. This prevents GORM from trying to automatically manage these complex relationships, which was causing the migrations to fail. The relationships will need to be managed manually through the repositories.
-   Added a new test file `internal/data/sql/work_repository_test.go` with tests for the `WorkRepository`. This includes tests for the `Create`, `GetByID`, `Update`, and `Delete` methods.
-   The tests for the `internal/data/sql` package are now passing.

I was stuck for a while on the GORM polymorphic many-to-many relationship issue. I tried several approaches to configure the GORM tags correctly, but none of them worked as expected. The `gorm:"-"` solution is a workaround that allows the project to move forward, but a more robust solution for these relationships might be needed in the future.
2025-09-06 03:56:01 +00:00

1054 lines
34 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) BeforeSave(tx *gorm.DB) error {
if u.Password == "" {
return nil
}
if len(u.Password) >= 60 && u.Password[:4] == "$2a$" {
return nil
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return errors.New("failed to hash password: " + err.Error())
}
u.Password = string(hashedPassword)
return nil
}
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
return err == nil
}
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 {
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 []Translation `gorm:"polymorphic:Translatable"`
Authors []*Author `gorm:"many2many:work_authors"`
Tags []*Tag `gorm:"many2many:work_tags"`
Categories []*Category `gorm:"many2many:work_categories"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
}
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
Works []*Work `gorm:"many2many:work_authors"`
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:"-"`
Monetizations []Monetization `gorm:"-"`
}
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
Works []*Work `gorm:"many2many:book_works"`
Authors []*Author `gorm:"many2many:book_authors"`
PublisherID *uint
Publisher *Publisher `gorm:"foreignKey:PublisherID"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
}
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:"-"`
Monetizations []Monetization `gorm:"-"`
}
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'"`
Works []*Work `gorm:"many2many:work_sources"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
}
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 (w *Work) BeforeSave(tx *gorm.DB) error {
if w.Title == "" {
w.Title = "Untitled Work"
}
return nil
}
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
Work *Work `gorm:"foreignKey:WorkID"`
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"`
Work *Work `gorm:"foreignKey:WorkID"`
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"`
Work *Work `gorm:"foreignKey:WorkID"`
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"`
Works []*Work `gorm:"many2many:collection_works"`
IsPublic bool `gorm:"default:true"`
CoverImageURL string `gorm:"size:255"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
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"`
Works []*Work `gorm:"many2many:work_tags"`
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"`
Works []*Work `gorm:"many2many:work_categories"`
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 WorkSeries struct {
BaseModel
WorkID uint `gorm:"index;uniqueIndex:uniq_work_series"`
Work *Work `gorm:"foreignKey:WorkID"`
SeriesID uint `gorm:"index;uniqueIndex:uniq_work_series"`
Series *Series `gorm:"foreignKey:SeriesID"`
NumberInSeries int `gorm:"default:0"`
}
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"`
Status TranslationStatus `gorm:"size:50;default:'draft'"`
PublishedAt *time.Time
TranslatableID uint `gorm:"not null"`
TranslatableType string `gorm:"size:50;not null"`
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 (w *Work) GetID() uint { return w.ID }
func (w *Work) GetType() string { return "Work" }
func (w *Work) GetDefaultLanguage() string { return w.Language }
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
Copyrightables []Copyrightable `gorm:"-"`
Translations []CopyrightTranslation `gorm:"foreignKey:CopyrightID"`
}
type Copyrightable struct {
BaseModel
CopyrightID uint
Copyright *Copyright `gorm:"foreignKey:CopyrightID"`
CopyrightableID uint
CopyrightableType string
}
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"`
Claimables []Copyrightable `gorm:"-"`
}
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 Monetizable struct {
BaseModel
MonetizationID uint
Monetization *Monetization `gorm:"foreignKey:MonetizationID"`
MonetizableID uint
MonetizableType string
}
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"`
Monetizables []Monetizable `gorm:"-"`
}
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 WorkStats struct {
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"`
WorkID uint `gorm:"uniqueIndex;index"`
Work *Work `gorm:"foreignKey:WorkID"`
}
type TranslationStats struct {
BaseModel
Views int64 `gorm:"default:0"`
Likes int64 `gorm:"default:0"`
Comments int64 `gorm:"default:0"`
Shares int64 `gorm:"default:0"`
TranslationID uint `gorm:"uniqueIndex;index"`
Translation *Translation `gorm:"foreignKey:TranslationID"`
}
type UserStats struct {
BaseModel
Activity int64 `gorm:"default:0"`
Works int64 `gorm:"default:0"`
Translations int64 `gorm:"default:0"`
Comments int64 `gorm:"default:0"`
Likes int64 `gorm:"default:0"`
Bookmarks int64 `gorm:"default:0"`
UserID uint `gorm:"uniqueIndex;index"`
User *User `gorm:"foreignKey:UserID"`
}
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 BookWork struct {
BaseModel
BookID uint `gorm:"index;uniqueIndex:uniq_book_work"`
Book *Book `gorm:"foreignKey:BookID"`
WorkID uint `gorm:"index;uniqueIndex:uniq_book_work"`
Work *Work `gorm:"foreignKey:WorkID"`
Order int `gorm:"default:0"`
}
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 WorkAuthor struct {
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 *Author `gorm:"foreignKey:AuthorID"`
Role string `gorm:"size:50;default:'author';uniqueIndex:uniq_work_author_role"`
Ordinal int `gorm:"default:0"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
Data JSONB `gorm:"type:jsonb;default:'{}'"`
}
type TextBlock struct {
BaseModel
WorkID *uint
Work *Work `gorm:"foreignKey:WorkID"`
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
Work *Work `gorm:"foreignKey:WorkID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
}
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"`
Works []*Work `gorm:"many2many:work_words"`
}
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"`
Works []*Work `gorm:"many2many:work_concepts"`
}
type LanguageEntity struct {
BaseModel
Name string `gorm:"size:100;not null"`
Type string `gorm:"size:50"`
Language string `gorm:"size:50;not null"`
Works []*Work `gorm:"many2many:work_language_entities"`
}
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"`
Work *Work `gorm:"foreignKey:WorkID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
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"`
Works []*Work `gorm:"many2many:work_moods"`
}
type TopicCluster struct {
BaseModel
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Keywords string `gorm:"type:text"`
Works []*Work `gorm:"many2many:work_topic_clusters"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
}
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
Work *Work `gorm:"foreignKey:WorkID"`
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
Work *Work `gorm:"foreignKey:WorkID"`
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
Work *Work `gorm:"foreignKey:WorkID"`
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
Work *Work `gorm:"foreignKey:WorkID"`
TranslationID *uint
Translation *Translation `gorm:"foreignKey:TranslationID"`
}