package sql import ( "context" "errors" "fmt" "tercul/internal/domain" "tercul/internal/domain/work" "gorm.io/gorm" ) type workRepository struct { domain.BaseRepository[work.Work] db *gorm.DB } // NewWorkRepository creates a new WorkRepository. func NewWorkRepository(db *gorm.DB) work.WorkRepository { return &workRepository{ BaseRepository: NewBaseRepositoryImpl[work.Work](db), db: db, } } // FindByTitle finds works by title (partial match) func (r *workRepository) FindByTitle(ctx context.Context, title string) ([]work.Work, error) { var works []work.Work if err := r.db.WithContext(ctx).Where("title LIKE ?", "%"+title+"%").Find(&works).Error; err != nil { return nil, err } return works, nil } // FindByAuthor finds works by author ID func (r *workRepository) FindByAuthor(ctx context.Context, authorID uint) ([]work.Work, error) { var works []work.Work if err := r.db.WithContext(ctx).Joins("JOIN work_authors ON work_authors.work_id = works.id"). Where("work_authors.author_id = ?", authorID). Find(&works).Error; err != nil { return nil, err } return works, nil } // FindByCategory finds works by category ID func (r *workRepository) FindByCategory(ctx context.Context, categoryID uint) ([]work.Work, error) { var works []work.Work if err := r.db.WithContext(ctx).Joins("JOIN work_categories ON work_categories.work_id = works.id"). Where("work_categories.category_id = ?", categoryID). Find(&works).Error; err != nil { return nil, err } return works, nil } // FindByLanguage finds works by language with pagination func (r *workRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } var works []work.Work var totalCount int64 // Get total count if err := r.db.WithContext(ctx).Model(&work.Work{}).Where("language = ?", language).Count(&totalCount).Error; err != nil { return nil, err } // Calculate offset offset := (page - 1) * pageSize // Get paginated data if err := r.db.WithContext(ctx).Where("language = ?", language). Offset(offset).Limit(pageSize). Find(&works).Error; err != nil { return nil, err } // Calculate total pages totalPages := int(totalCount) / pageSize if int(totalCount)%pageSize > 0 { totalPages++ } hasNext := page < totalPages hasPrev := page > 1 return &domain.PaginatedResult[work.Work]{ Items: works, TotalCount: totalCount, Page: page, PageSize: pageSize, TotalPages: totalPages, HasNext: hasNext, HasPrev: hasPrev, }, nil } // Delete removes a work and its associations func (r *workRepository) Delete(ctx context.Context, id uint) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // Manually delete associations if err := tx.Select("Copyrights", "Monetizations", "Authors", "Tags", "Categories").Delete(&work.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: id}}}).Error; err != nil { return err } // Also delete the work itself if err := tx.Delete(&work.Work{}, id).Error; err != nil { return err } return nil }) } // GetWithTranslations gets a work with its translations func (r *workRepository) GetWithTranslations(ctx context.Context, id uint) (*work.Work, error) { return r.FindWithPreload(ctx, []string{"Translations"}, id) } // GetWithAssociations gets a work with all of its direct and many-to-many associations. func (r *workRepository) GetWithAssociations(ctx context.Context, id uint) (*work.Work, error) { associations := []string{ "Translations", "Authors", "Tags", "Categories", "Copyrights", "Monetizations", } return r.FindWithPreload(ctx, associations, id) } // GetWithAssociationsInTx gets a work with all associations within a transaction. func (r *workRepository) GetWithAssociationsInTx(ctx context.Context, tx *gorm.DB, id uint) (*work.Work, error) { var entity work.Work query := tx.WithContext(ctx) associations := []string{ "Translations", "Authors", "Tags", "Categories", "Copyrights", "Monetizations", } for _, preload := range associations { query = query.Preload(preload) } if err := query.First(&entity, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrEntityNotFound } return nil, fmt.Errorf("%w: %v", ErrDatabaseOperation, err) } return &entity, nil } // IsAuthor checks if a user is an author of a work. // Note: This assumes a direct relationship between user ID and author ID, // which may need to be revised based on the actual domain model. func (r *workRepository) IsAuthor(ctx context.Context, workID uint, authorID uint) (bool, error) { var count int64 err := r.db.WithContext(ctx). Table("work_authors"). Where("work_id = ? AND author_id = ?", workID, authorID). Count(&count).Error if err != nil { return false, err } return count > 0, nil } // ListWithTranslations lists works with their translations func (r *workRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[work.Work], error) { if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } var works []work.Work var totalCount int64 // Get total count if err := r.db.WithContext(ctx).Model(&work.Work{}).Count(&totalCount).Error; err != nil { return nil, err } // Calculate offset offset := (page - 1) * pageSize // Get paginated data with preloaded translations if err := r.db.WithContext(ctx).Preload("Translations"). Offset(offset).Limit(pageSize). Find(&works).Error; err != nil { return nil, err } // Calculate total pages totalPages := int(totalCount) / pageSize if int(totalCount)%pageSize > 0 { totalPages++ } hasNext := page < totalPages hasPrev := page > 1 return &domain.PaginatedResult[work.Work]{ Items: works, TotalCount: totalCount, Page: page, PageSize: pageSize, TotalPages: totalPages, HasNext: hasNext, HasPrev: hasPrev, }, nil }