This commit addresses the "Stabilize non-linguistics tests and interfaces" task from TODO.md.

The main changes are:
-   Refactored the `Copyright` and `Monetization` relationships to use explicit join tables for each owning model, as per the "Option A" strategy. This fixes the GORM migration issues related to polymorphic many-to-many relationships.
-   Created new join table structs (e.g., `WorkCopyright`, `AuthorCopyright`, `WorkMonetization`, etc.).
-   Updated the domain models to use standard `gorm:"many2many"` tags with the new join tables.
-   Refactored the `CopyrightRepository` and `MonetizationRepository` to use the new association-based logic.
-   Updated the application services (`CopyrightCommands`, `CopyrightQueries`, `MonetizationCommands`, `MonetizationQueries`) to use the new repository methods.
-   Consolidated all repository interfaces into a single `internal/domain/interfaces.go` file for better code organization.
-   Added extensive integration tests for the new repository and application layer logic for `Copyrights` and `Monetizations`.
-   Fixed the deletion logic for `WorkRepository` to correctly handle cascading deletes with SQLite.
-   Updated the `TODO.md` file to mark the "Stabilize non-linguistics tests and interfaces" task as complete.
This commit is contained in:
google-labs-jules[bot] 2025-09-06 06:25:11 +00:00
parent 042773c8f9
commit 5d6a6ef47b
69 changed files with 1739 additions and 635 deletions

View File

@ -4,6 +4,7 @@ import (
"tercul/internal/app/auth"
"tercul/internal/app/copyright"
"tercul/internal/app/localization"
"tercul/internal/app/monetization"
"tercul/internal/app/search"
"tercul/internal/app/work"
"tercul/internal/domain"
@ -26,4 +27,8 @@ type Application struct {
UserRepo domain.UserRepository
TagRepo domain.TagRepository
CategoryRepo domain.CategoryRepository
BookRepo domain.BookRepository
PublisherRepo domain.PublisherRepository
SourceRepo domain.SourceRepository
MonetizationQueries *monetization.MonetizationQueries
}

View File

@ -4,6 +4,7 @@ import (
"tercul/internal/app/auth"
"tercul/internal/app/copyright"
"tercul/internal/app/localization"
"tercul/internal/app/monetization"
"tercul/internal/app/search"
"tercul/internal/app/work"
"tercul/internal/data/sql"
@ -122,7 +123,10 @@ func (b *ApplicationBuilder) BuildApplication() error {
authQueries := auth.NewAuthQueries(userRepo, jwtManager)
copyrightCommands := copyright.NewCopyrightCommands(copyrightRepo)
copyrightQueries := copyright.NewCopyrightQueries(copyrightRepo)
bookRepo := sql.NewBookRepository(b.dbConn)
publisherRepo := sql.NewPublisherRepository(b.dbConn)
sourceRepo := sql.NewSourceRepository(b.dbConn)
copyrightQueries := copyright.NewCopyrightQueries(copyrightRepo, workRepo, authorRepo, bookRepo, publisherRepo, sourceRepo)
localizationService := localization.NewService(translationRepo)
@ -141,6 +145,10 @@ func (b *ApplicationBuilder) BuildApplication() error {
UserRepo: userRepo,
TagRepo: tagRepo,
CategoryRepo: categoryRepo,
BookRepo: sql.NewBookRepository(b.dbConn),
PublisherRepo: sql.NewPublisherRepository(b.dbConn),
SourceRepo: sql.NewSourceRepository(b.dbConn),
MonetizationQueries: monetization.NewMonetizationQueries(sql.NewMonetizationRepository(b.dbConn), workRepo, authorRepo, bookRepo, publisherRepo, sourceRepo),
}
log.LogInfo("Application layer initialized successfully")

View File

@ -55,26 +55,85 @@ func (c *CopyrightCommands) DeleteCopyright(ctx context.Context, id uint) error
return c.repo.Delete(ctx, id)
}
// AttachCopyrightToEntity attaches a copyright to any entity type.
func (c *CopyrightCommands) AttachCopyrightToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
if copyrightID == 0 || entityID == 0 {
return errors.New("invalid copyright ID or entity ID")
// AddCopyrightToWork adds a copyright to a work.
func (c *CopyrightCommands) AddCopyrightToWork(ctx context.Context, workID uint, copyrightID uint) error {
if workID == 0 || copyrightID == 0 {
return errors.New("invalid work ID or copyright ID")
}
if entityType == "" {
return errors.New("entity type cannot be empty")
}
return c.repo.AttachToEntity(ctx, copyrightID, entityID, entityType)
return c.repo.AddCopyrightToWork(ctx, workID, copyrightID)
}
// DetachCopyrightFromEntity removes a copyright from an entity.
func (c *CopyrightCommands) DetachCopyrightFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
if copyrightID == 0 || entityID == 0 {
return errors.New("invalid copyright ID or entity ID")
// RemoveCopyrightFromWork removes a copyright from a work.
func (c *CopyrightCommands) RemoveCopyrightFromWork(ctx context.Context, workID uint, copyrightID uint) error {
if workID == 0 || copyrightID == 0 {
return errors.New("invalid work ID or copyright ID")
}
if entityType == "" {
return errors.New("entity type cannot be empty")
return c.repo.RemoveCopyrightFromWork(ctx, workID, copyrightID)
}
// AddCopyrightToAuthor adds a copyright to an author.
func (c *CopyrightCommands) AddCopyrightToAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
if authorID == 0 || copyrightID == 0 {
return errors.New("invalid author ID or copyright ID")
}
return c.repo.DetachFromEntity(ctx, copyrightID, entityID, entityType)
return c.repo.AddCopyrightToAuthor(ctx, authorID, copyrightID)
}
// RemoveCopyrightFromAuthor removes a copyright from an author.
func (c *CopyrightCommands) RemoveCopyrightFromAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
if authorID == 0 || copyrightID == 0 {
return errors.New("invalid author ID or copyright ID")
}
return c.repo.RemoveCopyrightFromAuthor(ctx, authorID, copyrightID)
}
// AddCopyrightToBook adds a copyright to a book.
func (c *CopyrightCommands) AddCopyrightToBook(ctx context.Context, bookID uint, copyrightID uint) error {
if bookID == 0 || copyrightID == 0 {
return errors.New("invalid book ID or copyright ID")
}
return c.repo.AddCopyrightToBook(ctx, bookID, copyrightID)
}
// RemoveCopyrightFromBook removes a copyright from a book.
func (c *CopyrightCommands) RemoveCopyrightFromBook(ctx context.Context, bookID uint, copyrightID uint) error {
if bookID == 0 || copyrightID == 0 {
return errors.New("invalid book ID or copyright ID")
}
return c.repo.RemoveCopyrightFromBook(ctx, bookID, copyrightID)
}
// AddCopyrightToPublisher adds a copyright to a publisher.
func (c *CopyrightCommands) AddCopyrightToPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
if publisherID == 0 || copyrightID == 0 {
return errors.New("invalid publisher ID or copyright ID")
}
return c.repo.AddCopyrightToPublisher(ctx, publisherID, copyrightID)
}
// RemoveCopyrightFromPublisher removes a copyright from a publisher.
func (c *CopyrightCommands) RemoveCopyrightFromPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
if publisherID == 0 || copyrightID == 0 {
return errors.New("invalid publisher ID or copyright ID")
}
return c.repo.RemoveCopyrightFromPublisher(ctx, publisherID, copyrightID)
}
// AddCopyrightToSource adds a copyright to a source.
func (c *CopyrightCommands) AddCopyrightToSource(ctx context.Context, sourceID uint, copyrightID uint) error {
if sourceID == 0 || copyrightID == 0 {
return errors.New("invalid source ID or copyright ID")
}
return c.repo.AddCopyrightToSource(ctx, sourceID, copyrightID)
}
// RemoveCopyrightFromSource removes a copyright from a source.
func (c *CopyrightCommands) RemoveCopyrightFromSource(ctx context.Context, sourceID uint, copyrightID uint) error {
if sourceID == 0 || copyrightID == 0 {
return errors.New("invalid source ID or copyright ID")
}
return c.repo.RemoveCopyrightFromSource(ctx, sourceID, copyrightID)
}
// AddTranslation adds a translation to a copyright.

View File

@ -0,0 +1,237 @@
package copyright_test
import (
"context"
"testing"
"tercul/internal/app/copyright"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type CopyrightCommandsTestSuite struct {
testutil.IntegrationTestSuite
commands *copyright.CopyrightCommands
}
func (s *CopyrightCommandsTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
s.commands = copyright.NewCopyrightCommands(s.CopyrightRepo)
}
func (s *CopyrightCommandsTestSuite) TestAddCopyrightToWork() {
s.Run("should add a copyright to a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
// Act
err := s.commands.AddCopyrightToWork(context.Background(), work.ID, copyright.ID)
// Assert
s.Require().NoError(err)
// Verify that the association was created in the database
var foundWork domain.Work
err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Require().Len(foundWork.Copyrights, 1)
s.Equal(copyright.ID, foundWork.Copyrights[0].ID)
})
}
func (s *CopyrightCommandsTestSuite) TestRemoveCopyrightFromWork() {
s.Run("should remove a copyright from a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
s.Require().NoError(s.commands.AddCopyrightToWork(context.Background(), work.ID, copyright.ID))
// Act
err := s.commands.RemoveCopyrightFromWork(context.Background(), work.ID, copyright.ID)
// Assert
s.Require().NoError(err)
// Verify that the association was removed from the database
var foundWork domain.Work
err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Require().Len(foundWork.Copyrights, 0)
})
}
func (s *CopyrightCommandsTestSuite) TestAddCopyrightToAuthor() {
s.Run("should add a copyright to an author", func() {
// Arrange
author := &domain.Author{Name: "Test Author"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
// Act
err := s.commands.AddCopyrightToAuthor(context.Background(), author.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.Preload("Copyrights").First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Require().Len(foundAuthor.Copyrights, 1)
s.Equal(copyright.ID, foundAuthor.Copyrights[0].ID)
})
}
func (s *CopyrightCommandsTestSuite) TestRemoveCopyrightFromAuthor() {
s.Run("should remove a copyright from an author", func() {
// Arrange
author := &domain.Author{Name: "Test Author"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
s.Require().NoError(s.commands.AddCopyrightToAuthor(context.Background(), author.ID, copyright.ID))
// Act
err := s.commands.RemoveCopyrightFromAuthor(context.Background(), author.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.Preload("Copyrights").First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Require().Len(foundAuthor.Copyrights, 0)
})
}
func (s *CopyrightCommandsTestSuite) TestAddCopyrightToBook() {
s.Run("should add a copyright to a book", func() {
// Arrange
book := &domain.Book{Title: "Test Book"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
// Act
err := s.commands.AddCopyrightToBook(context.Background(), book.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.Preload("Copyrights").First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Require().Len(foundBook.Copyrights, 1)
s.Equal(copyright.ID, foundBook.Copyrights[0].ID)
})
}
func (s *CopyrightCommandsTestSuite) TestRemoveCopyrightFromBook() {
s.Run("should remove a copyright from a book", func() {
// Arrange
book := &domain.Book{Title: "Test Book"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
s.Require().NoError(s.commands.AddCopyrightToBook(context.Background(), book.ID, copyright.ID))
// Act
err := s.commands.RemoveCopyrightFromBook(context.Background(), book.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.Preload("Copyrights").First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Require().Len(foundBook.Copyrights, 0)
})
}
func (s *CopyrightCommandsTestSuite) TestAddCopyrightToPublisher() {
s.Run("should add a copyright to a publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "Test Publisher"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
// Act
err := s.commands.AddCopyrightToPublisher(context.Background(), publisher.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.Preload("Copyrights").First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Require().Len(foundPublisher.Copyrights, 1)
s.Equal(copyright.ID, foundPublisher.Copyrights[0].ID)
})
}
func (s *CopyrightCommandsTestSuite) TestRemoveCopyrightFromPublisher() {
s.Run("should remove a copyright from a publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "Test Publisher"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
s.Require().NoError(s.commands.AddCopyrightToPublisher(context.Background(), publisher.ID, copyright.ID))
// Act
err := s.commands.RemoveCopyrightFromPublisher(context.Background(), publisher.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.Preload("Copyrights").First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Require().Len(foundPublisher.Copyrights, 0)
})
}
func (s *CopyrightCommandsTestSuite) TestAddCopyrightToSource() {
s.Run("should add a copyright to a source", func() {
// Arrange
source := &domain.Source{Name: "Test Source"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
// Act
err := s.commands.AddCopyrightToSource(context.Background(), source.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.Preload("Copyrights").First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Require().Len(foundSource.Copyrights, 1)
s.Equal(copyright.ID, foundSource.Copyrights[0].ID)
})
}
func (s *CopyrightCommandsTestSuite) TestRemoveCopyrightFromSource() {
s.Run("should remove a copyright from a source", func() {
// Arrange
source := &domain.Source{Name: "Test Source"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
copyright := &domain.Copyright{Name: "Test Copyright", Identificator: "TC-123"}
s.Require().NoError(s.CopyrightRepo.Create(context.Background(), copyright))
s.Require().NoError(s.commands.AddCopyrightToSource(context.Background(), source.ID, copyright.ID))
// Act
err := s.commands.RemoveCopyrightFromSource(context.Background(), source.ID, copyright.ID)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.Preload("Copyrights").First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Require().Len(foundSource.Copyrights, 0)
})
}
func TestCopyrightCommands(t *testing.T) {
suite.Run(t, new(CopyrightCommandsTestSuite))
}

View File

@ -8,12 +8,17 @@ import (
// CopyrightQueries contains the query handlers for copyright.
type CopyrightQueries struct {
repo domain.CopyrightRepository
repo domain.CopyrightRepository
workRepo domain.WorkRepository
authorRepo domain.AuthorRepository
bookRepo domain.BookRepository
publisherRepo domain.PublisherRepository
sourceRepo domain.SourceRepository
}
// NewCopyrightQueries creates a new CopyrightQueries handler.
func NewCopyrightQueries(repo domain.CopyrightRepository) *CopyrightQueries {
return &CopyrightQueries{repo: repo}
func NewCopyrightQueries(repo domain.CopyrightRepository, workRepo domain.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *CopyrightQueries {
return &CopyrightQueries{repo: repo, workRepo: workRepo, authorRepo: authorRepo, bookRepo: bookRepo, publisherRepo: publisherRepo, sourceRepo: sourceRepo}
}
// GetCopyrightByID retrieves a copyright by ID.
@ -31,23 +36,51 @@ func (q *CopyrightQueries) ListCopyrights(ctx context.Context) ([]domain.Copyrig
return q.repo.ListAll(ctx)
}
// GetCopyrightsForEntity gets all copyrights for a specific entity.
func (q *CopyrightQueries) GetCopyrightsForEntity(ctx context.Context, entityID uint, entityType string) ([]domain.Copyright, error) {
if entityID == 0 {
return nil, errors.New("invalid entity ID")
// GetCopyrightsForWork gets all copyrights for a specific work.
func (q *CopyrightQueries) GetCopyrightsForWork(ctx context.Context, workID uint) ([]*domain.Copyright, error) {
work, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
if err != nil {
return nil, err
}
if entityType == "" {
return nil, errors.New("entity type cannot be empty")
}
return q.repo.GetByEntity(ctx, entityID, entityType)
return work.Copyrights, nil
}
// GetEntitiesByCopyright gets all entities that have a specific copyright.
func (q *CopyrightQueries) GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]domain.Copyrightable, error) {
if copyrightID == 0 {
return nil, errors.New("invalid copyright ID")
// GetCopyrightsForAuthor gets all copyrights for a specific author.
func (q *CopyrightQueries) GetCopyrightsForAuthor(ctx context.Context, authorID uint) ([]*domain.Copyright, error) {
author, err := q.authorRepo.GetByIDWithOptions(ctx, authorID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
if err != nil {
return nil, err
}
return q.repo.GetEntitiesByCopyright(ctx, copyrightID)
return author.Copyrights, nil
}
// GetCopyrightsForBook gets all copyrights for a specific book.
func (q *CopyrightQueries) GetCopyrightsForBook(ctx context.Context, bookID uint) ([]*domain.Copyright, error) {
book, err := q.bookRepo.GetByIDWithOptions(ctx, bookID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
if err != nil {
return nil, err
}
return book.Copyrights, nil
}
// GetCopyrightsForPublisher gets all copyrights for a specific publisher.
func (q *CopyrightQueries) GetCopyrightsForPublisher(ctx context.Context, publisherID uint) ([]*domain.Copyright, error) {
publisher, err := q.publisherRepo.GetByIDWithOptions(ctx, publisherID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
if err != nil {
return nil, err
}
return publisher.Copyrights, nil
}
// GetCopyrightsForSource gets all copyrights for a specific source.
func (q *CopyrightQueries) GetCopyrightsForSource(ctx context.Context, sourceID uint) ([]*domain.Copyright, error) {
source, err := q.sourceRepo.GetByIDWithOptions(ctx, sourceID, &domain.QueryOptions{Preloads: []string{"Copyrights"}})
if err != nil {
return nil, err
}
return source.Copyrights, nil
}
// GetTranslations gets all translations for a copyright.

View File

@ -0,0 +1,89 @@
package monetization
import (
"context"
"errors"
"tercul/internal/domain"
)
// MonetizationCommands contains the command handlers for monetization.
type MonetizationCommands struct {
repo domain.MonetizationRepository
}
// NewMonetizationCommands creates a new MonetizationCommands handler.
func NewMonetizationCommands(repo domain.MonetizationRepository) *MonetizationCommands {
return &MonetizationCommands{repo: repo}
}
// AddMonetizationToWork adds a monetization to a work.
func (c *MonetizationCommands) AddMonetizationToWork(ctx context.Context, workID uint, monetizationID uint) error {
if workID == 0 || monetizationID == 0 {
return errors.New("invalid work ID or monetization ID")
}
return c.repo.AddMonetizationToWork(ctx, workID, monetizationID)
}
// RemoveMonetizationFromWork removes a monetization from a work.
func (c *MonetizationCommands) RemoveMonetizationFromWork(ctx context.Context, workID uint, monetizationID uint) error {
if workID == 0 || monetizationID == 0 {
return errors.New("invalid work ID or monetization ID")
}
return c.repo.RemoveMonetizationFromWork(ctx, workID, monetizationID)
}
func (c *MonetizationCommands) AddMonetizationToAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
if authorID == 0 || monetizationID == 0 {
return errors.New("invalid author ID or monetization ID")
}
return c.repo.AddMonetizationToAuthor(ctx, authorID, monetizationID)
}
func (c *MonetizationCommands) RemoveMonetizationFromAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
if authorID == 0 || monetizationID == 0 {
return errors.New("invalid author ID or monetization ID")
}
return c.repo.RemoveMonetizationFromAuthor(ctx, authorID, monetizationID)
}
func (c *MonetizationCommands) AddMonetizationToBook(ctx context.Context, bookID uint, monetizationID uint) error {
if bookID == 0 || monetizationID == 0 {
return errors.New("invalid book ID or monetization ID")
}
return c.repo.AddMonetizationToBook(ctx, bookID, monetizationID)
}
func (c *MonetizationCommands) RemoveMonetizationFromBook(ctx context.Context, bookID uint, monetizationID uint) error {
if bookID == 0 || monetizationID == 0 {
return errors.New("invalid book ID or monetization ID")
}
return c.repo.RemoveMonetizationFromBook(ctx, bookID, monetizationID)
}
func (c *MonetizationCommands) AddMonetizationToPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
if publisherID == 0 || monetizationID == 0 {
return errors.New("invalid publisher ID or monetization ID")
}
return c.repo.AddMonetizationToPublisher(ctx, publisherID, monetizationID)
}
func (c *MonetizationCommands) RemoveMonetizationFromPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
if publisherID == 0 || monetizationID == 0 {
return errors.New("invalid publisher ID or monetization ID")
}
return c.repo.RemoveMonetizationFromPublisher(ctx, publisherID, monetizationID)
}
func (c *MonetizationCommands) AddMonetizationToSource(ctx context.Context, sourceID uint, monetizationID uint) error {
if sourceID == 0 || monetizationID == 0 {
return errors.New("invalid source ID or monetization ID")
}
return c.repo.AddMonetizationToSource(ctx, sourceID, monetizationID)
}
func (c *MonetizationCommands) RemoveMonetizationFromSource(ctx context.Context, sourceID uint, monetizationID uint) error {
if sourceID == 0 || monetizationID == 0 {
return errors.New("invalid source ID or monetization ID")
}
return c.repo.RemoveMonetizationFromSource(ctx, sourceID, monetizationID)
}

View File

@ -0,0 +1,215 @@
package monetization_test
import (
"context"
"testing"
"tercul/internal/app/monetization"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type MonetizationCommandsTestSuite struct {
testutil.IntegrationTestSuite
commands *monetization.MonetizationCommands
}
func (s *MonetizationCommandsTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
s.commands = monetization.NewMonetizationCommands(s.MonetizationRepo)
}
func (s *MonetizationCommandsTestSuite) TestAddMonetizationToWork() {
s.Run("should add a monetization to a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.commands.AddMonetizationToWork(context.Background(), work.ID, monetization.ID)
// Assert
s.Require().NoError(err)
// Verify that the association was created in the database
var foundWork domain.Work
err = s.DB.Preload("Monetizations").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Require().Len(foundWork.Monetizations, 1)
s.Equal(monetization.ID, foundWork.Monetizations[0].ID)
})
}
func (s *MonetizationCommandsTestSuite) TestAddMonetizationToAuthor() {
s.Run("should add a monetization to an author", func() {
// Arrange
author := &domain.Author{Name: "Test Author"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.commands.AddMonetizationToAuthor(context.Background(), author.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.Preload("Monetizations").First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Require().Len(foundAuthor.Monetizations, 1)
s.Equal(monetization.ID, foundAuthor.Monetizations[0].ID)
})
}
func (s *MonetizationCommandsTestSuite) TestRemoveMonetizationFromAuthor() {
s.Run("should remove a monetization from an author", func() {
// Arrange
author := &domain.Author{Name: "Test Author"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
s.Require().NoError(s.commands.AddMonetizationToAuthor(context.Background(), author.ID, monetization.ID))
// Act
err := s.commands.RemoveMonetizationFromAuthor(context.Background(), author.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.Preload("Monetizations").First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Require().Len(foundAuthor.Monetizations, 0)
})
}
func (s *MonetizationCommandsTestSuite) TestAddMonetizationToBook() {
s.Run("should add a monetization to a book", func() {
// Arrange
book := &domain.Book{Title: "Test Book"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.commands.AddMonetizationToBook(context.Background(), book.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.Preload("Monetizations").First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Require().Len(foundBook.Monetizations, 1)
s.Equal(monetization.ID, foundBook.Monetizations[0].ID)
})
}
func (s *MonetizationCommandsTestSuite) TestRemoveMonetizationFromBook() {
s.Run("should remove a monetization from a book", func() {
// Arrange
book := &domain.Book{Title: "Test Book"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
s.Require().NoError(s.commands.AddMonetizationToBook(context.Background(), book.ID, monetization.ID))
// Act
err := s.commands.RemoveMonetizationFromBook(context.Background(), book.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.Preload("Monetizations").First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Require().Len(foundBook.Monetizations, 0)
})
}
func (s *MonetizationCommandsTestSuite) TestAddMonetizationToPublisher() {
s.Run("should add a monetization to a publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "Test Publisher"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.commands.AddMonetizationToPublisher(context.Background(), publisher.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.Preload("Monetizations").First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Require().Len(foundPublisher.Monetizations, 1)
s.Equal(monetization.ID, foundPublisher.Monetizations[0].ID)
})
}
func (s *MonetizationCommandsTestSuite) TestRemoveMonetizationFromPublisher() {
s.Run("should remove a monetization from a publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "Test Publisher"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
s.Require().NoError(s.commands.AddMonetizationToPublisher(context.Background(), publisher.ID, monetization.ID))
// Act
err := s.commands.RemoveMonetizationFromPublisher(context.Background(), publisher.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.Preload("Monetizations").First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Require().Len(foundPublisher.Monetizations, 0)
})
}
func (s *MonetizationCommandsTestSuite) TestAddMonetizationToSource() {
s.Run("should add a monetization to a source", func() {
// Arrange
source := &domain.Source{Name: "Test Source"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.commands.AddMonetizationToSource(context.Background(), source.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.Preload("Monetizations").First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Require().Len(foundSource.Monetizations, 1)
s.Equal(monetization.ID, foundSource.Monetizations[0].ID)
})
}
func (s *MonetizationCommandsTestSuite) TestRemoveMonetizationFromSource() {
s.Run("should remove a monetization from a source", func() {
// Arrange
source := &domain.Source{Name: "Test Source"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
s.Require().NoError(s.commands.AddMonetizationToSource(context.Background(), source.ID, monetization.ID))
// Act
err := s.commands.RemoveMonetizationFromSource(context.Background(), source.ID, monetization.ID)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.Preload("Monetizations").First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Require().Len(foundSource.Monetizations, 0)
})
}
func TestMonetizationCommands(t *testing.T) {
suite.Run(t, new(MonetizationCommandsTestSuite))
}

View File

@ -0,0 +1,75 @@
package monetization
import (
"context"
"errors"
"tercul/internal/domain"
)
// MonetizationQueries contains the query handlers for monetization.
type MonetizationQueries struct {
repo domain.MonetizationRepository
workRepo domain.WorkRepository
authorRepo domain.AuthorRepository
bookRepo domain.BookRepository
publisherRepo domain.PublisherRepository
sourceRepo domain.SourceRepository
}
// NewMonetizationQueries creates a new MonetizationQueries handler.
func NewMonetizationQueries(repo domain.MonetizationRepository, workRepo domain.WorkRepository, authorRepo domain.AuthorRepository, bookRepo domain.BookRepository, publisherRepo domain.PublisherRepository, sourceRepo domain.SourceRepository) *MonetizationQueries {
return &MonetizationQueries{repo: repo, workRepo: workRepo, authorRepo: authorRepo, bookRepo: bookRepo, publisherRepo: publisherRepo, sourceRepo: sourceRepo}
}
// GetMonetizationByID retrieves a monetization by ID.
func (q *MonetizationQueries) GetMonetizationByID(ctx context.Context, id uint) (*domain.Monetization, error) {
if id == 0 {
return nil, errors.New("invalid monetization ID")
}
return q.repo.GetByID(ctx, id)
}
// ListMonetizations retrieves all monetizations.
func (q *MonetizationQueries) ListMonetizations(ctx context.Context) ([]domain.Monetization, error) {
return q.repo.ListAll(ctx)
}
func (q *MonetizationQueries) GetMonetizationsForWork(ctx context.Context, workID uint) ([]*domain.Monetization, error) {
work, err := q.workRepo.GetByIDWithOptions(ctx, workID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
if err != nil {
return nil, err
}
return work.Monetizations, nil
}
func (q *MonetizationQueries) GetMonetizationsForAuthor(ctx context.Context, authorID uint) ([]*domain.Monetization, error) {
author, err := q.authorRepo.GetByIDWithOptions(ctx, authorID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
if err != nil {
return nil, err
}
return author.Monetizations, nil
}
func (q *MonetizationQueries) GetMonetizationsForBook(ctx context.Context, bookID uint) ([]*domain.Monetization, error) {
book, err := q.bookRepo.GetByIDWithOptions(ctx, bookID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
if err != nil {
return nil, err
}
return book.Monetizations, nil
}
func (q *MonetizationQueries) GetMonetizationsForPublisher(ctx context.Context, publisherID uint) ([]*domain.Monetization, error) {
publisher, err := q.publisherRepo.GetByIDWithOptions(ctx, publisherID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
if err != nil {
return nil, err
}
return publisher.Monetizations, nil
}
func (q *MonetizationQueries) GetMonetizationsForSource(ctx context.Context, sourceID uint) ([]*domain.Monetization, error) {
source, err := q.sourceRepo.GetByIDWithOptions(ctx, sourceID, &domain.QueryOptions{Preloads: []string{"Monetizations"}})
if err != nil {
return nil, err
}
return source.Monetizations, nil
}

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/author"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type authorRepository struct {
}
// NewAuthorRepository creates a new AuthorRepository.
func NewAuthorRepository(db *gorm.DB) author.AuthorRepository {
func NewAuthorRepository(db *gorm.DB) domain.AuthorRepository {
return &authorRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Author](db),
db: db,

View File

@ -0,0 +1,120 @@
package sql_test
import (
"context"
"testing"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type AuthorRepositoryTestSuite struct {
testutil.IntegrationTestSuite
}
func (s *AuthorRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
}
func (s *AuthorRepositoryTestSuite) TestCreateAuthor() {
s.Run("should create a new author", func() {
// Arrange
author := &domain.Author{
Name: "New Test Author",
TranslatableModel: domain.TranslatableModel{
Language: "en",
},
}
// Act
err := s.AuthorRepo.Create(context.Background(), author)
// Assert
s.Require().NoError(err)
s.NotZero(author.ID)
// Verify that the author was actually created in the database
var foundAuthor domain.Author
err = s.DB.First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Equal("New Test Author", foundAuthor.Name)
s.Equal("en", foundAuthor.Language)
})
}
func (s *AuthorRepositoryTestSuite) TestGetAuthorByID() {
s.Run("should return an author by ID", func() {
// Arrange
author := &domain.Author{Name: "Test Author"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
// Act
foundAuthor, err := s.AuthorRepo.GetByID(context.Background(), author.ID)
// Assert
s.Require().NoError(err)
s.Require().NotNil(foundAuthor)
s.Equal(author.ID, foundAuthor.ID)
s.Equal("Test Author", foundAuthor.Name)
})
}
func (s *AuthorRepositoryTestSuite) TestUpdateAuthor() {
s.Run("should update an existing author", func() {
// Arrange
author := &domain.Author{Name: "Original Name"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
author.Name = "Updated Name"
// Act
err := s.AuthorRepo.Update(context.Background(), author)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.First(&foundAuthor, author.ID).Error
s.Require().NoError(err)
s.Equal("Updated Name", foundAuthor.Name)
})
}
func (s *AuthorRepositoryTestSuite) TestDeleteAuthor() {
s.Run("should delete an existing author", func() {
// Arrange
author := &domain.Author{Name: "To Be Deleted"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author))
// Act
err := s.AuthorRepo.Delete(context.Background(), author.ID)
// Assert
s.Require().NoError(err)
var foundAuthor domain.Author
err = s.DB.First(&foundAuthor, author.ID).Error
s.Require().Error(err)
})
}
func (s *AuthorRepositoryTestSuite) TestListByWorkID() {
s.Run("should return all authors for a given work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
author1 := &domain.Author{Name: "Author 1"}
author2 := &domain.Author{Name: "Author 2"}
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author1))
s.Require().NoError(s.AuthorRepo.Create(context.Background(), author2))
s.Require().NoError(s.DB.Model(&work).Association("Authors").Append([]*domain.Author{author1, author2}))
// Act
authors, err := s.AuthorRepo.ListByWorkID(context.Background(), work.ID)
// Assert
s.Require().NoError(err)
s.Len(authors, 2)
})
}
func TestAuthorRepository(t *testing.T) {
suite.Run(t, new(AuthorRepositoryTestSuite))
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/book"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type bookRepository struct {
}
// NewBookRepository creates a new BookRepository.
func NewBookRepository(db *gorm.DB) book.BookRepository {
func NewBookRepository(db *gorm.DB) domain.BookRepository {
return &bookRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Book](db),
db: db,

View File

@ -0,0 +1,117 @@
package sql_test
import (
"context"
"testing"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type BookRepositoryTestSuite struct {
testutil.IntegrationTestSuite
}
func (s *BookRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
}
func (s *BookRepositoryTestSuite) TestCreateBook() {
s.Run("should create a new book", func() {
// Arrange
book := &domain.Book{
Title: "New Test Book",
TranslatableModel: domain.TranslatableModel{
Language: "en",
},
}
// Act
err := s.BookRepo.Create(context.Background(), book)
// Assert
s.Require().NoError(err)
s.NotZero(book.ID)
// Verify that the book was actually created in the database
var foundBook domain.Book
err = s.DB.First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Equal("New Test Book", foundBook.Title)
s.Equal("en", foundBook.Language)
})
}
func (s *BookRepositoryTestSuite) TestGetBookByID() {
s.Run("should return a book by ID", func() {
// Arrange
book := &domain.Book{Title: "Test Book"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
// Act
foundBook, err := s.BookRepo.GetByID(context.Background(), book.ID)
// Assert
s.Require().NoError(err)
s.Require().NotNil(foundBook)
s.Equal(book.ID, foundBook.ID)
s.Equal("Test Book", foundBook.Title)
})
}
func (s *BookRepositoryTestSuite) TestUpdateBook() {
s.Run("should update an existing book", func() {
// Arrange
book := &domain.Book{Title: "Original Title"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
book.Title = "Updated Title"
// Act
err := s.BookRepo.Update(context.Background(), book)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.First(&foundBook, book.ID).Error
s.Require().NoError(err)
s.Equal("Updated Title", foundBook.Title)
})
}
func (s *BookRepositoryTestSuite) TestDeleteBook() {
s.Run("should delete an existing book", func() {
// Arrange
book := &domain.Book{Title: "To Be Deleted"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
// Act
err := s.BookRepo.Delete(context.Background(), book.ID)
// Assert
s.Require().NoError(err)
var foundBook domain.Book
err = s.DB.First(&foundBook, book.ID).Error
s.Require().Error(err)
})
}
func (s *BookRepositoryTestSuite) TestFindByISBN() {
s.Run("should return a book by ISBN", func() {
// Arrange
book := &domain.Book{Title: "Test Book", ISBN: "1234567890"}
s.Require().NoError(s.BookRepo.Create(context.Background(), book))
// Act
foundBook, err := s.BookRepo.FindByISBN(context.Background(), "1234567890")
// Assert
s.Require().NoError(err)
s.Require().NotNil(foundBook)
s.Equal(book.ID, foundBook.ID)
})
}
func TestBookRepository(t *testing.T) {
suite.Run(t, new(BookRepositoryTestSuite))
}

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/bookmark"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type bookmarkRepository struct {
}
// NewBookmarkRepository creates a new BookmarkRepository.
func NewBookmarkRepository(db *gorm.DB) bookmark.BookmarkRepository {
func NewBookmarkRepository(db *gorm.DB) domain.BookmarkRepository {
return &bookmarkRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Bookmark](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/category"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type categoryRepository struct {
}
// NewCategoryRepository creates a new CategoryRepository.
func NewCategoryRepository(db *gorm.DB) category.CategoryRepository {
func NewCategoryRepository(db *gorm.DB) domain.CategoryRepository {
return &categoryRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Category](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/city"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type cityRepository struct {
}
// NewCityRepository creates a new CityRepository.
func NewCityRepository(db *gorm.DB) city.CityRepository {
func NewCityRepository(db *gorm.DB) domain.CityRepository {
return &cityRepository{
BaseRepository: NewBaseRepositoryImpl[domain.City](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/collection"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type collectionRepository struct {
}
// NewCollectionRepository creates a new CollectionRepository.
func NewCollectionRepository(db *gorm.DB) collection.CollectionRepository {
func NewCollectionRepository(db *gorm.DB) domain.CollectionRepository {
return &collectionRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Collection](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/comment"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type commentRepository struct {
}
// NewCommentRepository creates a new CommentRepository.
func NewCommentRepository(db *gorm.DB) comment.CommentRepository {
func NewCommentRepository(db *gorm.DB) domain.CommentRepository {
return &commentRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Comment](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/contribution"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type contributionRepository struct {
}
// NewContributionRepository creates a new ContributionRepository.
func NewContributionRepository(db *gorm.DB) contribution.ContributionRepository {
func NewContributionRepository(db *gorm.DB) domain.ContributionRepository {
return &contributionRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Contribution](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/copyright_claim"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type copyrightClaimRepository struct {
}
// NewCopyrightClaimRepository creates a new CopyrightClaimRepository.
func NewCopyrightClaimRepository(db *gorm.DB) copyright_claim.Copyright_claimRepository {
func NewCopyrightClaimRepository(db *gorm.DB) domain.CopyrightClaimRepository {
return &copyrightClaimRepository{
BaseRepository: NewBaseRepositoryImpl[domain.CopyrightClaim](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/copyright"
"gorm.io/gorm"
)
@ -15,45 +14,14 @@ type copyrightRepository struct {
}
// NewCopyrightRepository creates a new CopyrightRepository.
func NewCopyrightRepository(db *gorm.DB) copyright.CopyrightRepository {
func NewCopyrightRepository(db *gorm.DB) domain.CopyrightRepository {
return &copyrightRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Copyright](db),
db: db,
}
}
// AttachToEntity attaches a copyright to any entity type
func (r *copyrightRepository) AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
copyrightable := domain.Copyrightable{
CopyrightID: copyrightID,
CopyrightableID: entityID,
CopyrightableType: entityType,
}
return r.db.WithContext(ctx).Create(&copyrightable).Error
}
// DetachFromEntity removes a copyright from an entity
func (r *copyrightRepository) DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error {
return r.db.WithContext(ctx).Where("copyright_id = ? AND copyrightable_id = ? AND copyrightable_type = ?",
copyrightID, entityID, entityType).Delete(&domain.Copyrightable{}).Error
}
// GetByEntity gets all copyrights for a specific entity
func (r *copyrightRepository) GetByEntity(ctx context.Context, entityID uint, entityType string) ([]domain.Copyright, error) {
var copyrights []domain.Copyright
err := r.db.WithContext(ctx).Joins("JOIN copyrightables ON copyrightables.copyright_id = copyrights.id").
Where("copyrightables.copyrightable_id = ? AND copyrightables.copyrightable_type = ?", entityID, entityType).
Preload("Translations").
Find(&copyrights).Error
return copyrights, err
}
// GetEntitiesByCopyright gets all entities that have a specific copyright
func (r *copyrightRepository) GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]domain.Copyrightable, error) {
var copyrightables []domain.Copyrightable
err := r.db.WithContext(ctx).Where("copyright_id = ?", copyrightID).Find(&copyrightables).Error
return copyrightables, err
}
// AddTranslation adds a translation to a copyright
func (r *copyrightRepository) AddTranslation(ctx context.Context, translation *domain.CopyrightTranslation) error {
return r.db.WithContext(ctx).Create(translation).Error
@ -78,3 +46,63 @@ func (r *copyrightRepository) GetTranslationByLanguage(ctx context.Context, copy
}
return &translation, nil
}
func (r *copyrightRepository) AddCopyrightToWork(ctx context.Context, workID uint, copyrightID uint) error {
work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(work).Association("Copyrights").Append(copyright)
}
func (r *copyrightRepository) RemoveCopyrightFromWork(ctx context.Context, workID uint, copyrightID uint) error {
work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(work).Association("Copyrights").Delete(copyright)
}
func (r *copyrightRepository) AddCopyrightToAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
author := &domain.Author{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: authorID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(author).Association("Copyrights").Append(copyright)
}
func (r *copyrightRepository) RemoveCopyrightFromAuthor(ctx context.Context, authorID uint, copyrightID uint) error {
author := &domain.Author{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: authorID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(author).Association("Copyrights").Delete(copyright)
}
func (r *copyrightRepository) AddCopyrightToBook(ctx context.Context, bookID uint, copyrightID uint) error {
book := &domain.Book{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: bookID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(book).Association("Copyrights").Append(copyright)
}
func (r *copyrightRepository) RemoveCopyrightFromBook(ctx context.Context, bookID uint, copyrightID uint) error {
book := &domain.Book{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: bookID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(book).Association("Copyrights").Delete(copyright)
}
func (r *copyrightRepository) AddCopyrightToPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
publisher := &domain.Publisher{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: publisherID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(publisher).Association("Copyrights").Append(copyright)
}
func (r *copyrightRepository) RemoveCopyrightFromPublisher(ctx context.Context, publisherID uint, copyrightID uint) error {
publisher := &domain.Publisher{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: publisherID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(publisher).Association("Copyrights").Delete(copyright)
}
func (r *copyrightRepository) AddCopyrightToSource(ctx context.Context, sourceID uint, copyrightID uint) error {
source := &domain.Source{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: sourceID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(source).Association("Copyrights").Append(copyright)
}
func (r *copyrightRepository) RemoveCopyrightFromSource(ctx context.Context, sourceID uint, copyrightID uint) error {
source := &domain.Source{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: sourceID}}}
copyright := &domain.Copyright{BaseModel: domain.BaseModel{ID: copyrightID}}
return r.db.WithContext(ctx).Model(source).Association("Copyrights").Delete(copyright)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/country"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type countryRepository struct {
}
// NewCountryRepository creates a new CountryRepository.
func NewCountryRepository(db *gorm.DB) country.CountryRepository {
func NewCountryRepository(db *gorm.DB) domain.CountryRepository {
return &countryRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Country](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/edge"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type edgeRepository struct {
}
// NewEdgeRepository creates a new EdgeRepository.
func NewEdgeRepository(db *gorm.DB) edge.EdgeRepository {
func NewEdgeRepository(db *gorm.DB) domain.EdgeRepository {
return &edgeRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Edge](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/edition"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type editionRepository struct {
}
// NewEditionRepository creates a new EditionRepository.
func NewEditionRepository(db *gorm.DB) edition.EditionRepository {
func NewEditionRepository(db *gorm.DB) domain.EditionRepository {
return &editionRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Edition](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/email_verification"
"time"
"gorm.io/gorm"
@ -16,7 +15,7 @@ type emailVerificationRepository struct {
}
// NewEmailVerificationRepository creates a new EmailVerificationRepository.
func NewEmailVerificationRepository(db *gorm.DB) email_verification.Email_verificationRepository {
func NewEmailVerificationRepository(db *gorm.DB) domain.EmailVerificationRepository {
return &emailVerificationRepository{
BaseRepository: NewBaseRepositoryImpl[domain.EmailVerification](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/like"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type likeRepository struct {
}
// NewLikeRepository creates a new LikeRepository.
func NewLikeRepository(db *gorm.DB) like.LikeRepository {
func NewLikeRepository(db *gorm.DB) domain.LikeRepository {
return &likeRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Like](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/monetization"
"gorm.io/gorm"
)
@ -14,36 +13,69 @@ type monetizationRepository struct {
}
// NewMonetizationRepository creates a new MonetizationRepository.
func NewMonetizationRepository(db *gorm.DB) monetization.MonetizationRepository {
func NewMonetizationRepository(db *gorm.DB) domain.MonetizationRepository {
return &monetizationRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Monetization](db),
db: db,
}
}
// ListByWorkID finds monetizations by work ID
func (r *monetizationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Monetization, error) {
var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("work_id = ?", workID).Find(&monetizations).Error; err != nil {
return nil, err
}
return monetizations, nil
func (r *monetizationRepository) AddMonetizationToWork(ctx context.Context, workID uint, monetizationID uint) error {
work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(work).Association("Monetizations").Append(monetization)
}
// ListByTranslationID finds monetizations by translation ID
func (r *monetizationRepository) ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Monetization, error) {
var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("translation_id = ?", translationID).Find(&monetizations).Error; err != nil {
return nil, err
}
return monetizations, nil
func (r *monetizationRepository) RemoveMonetizationFromWork(ctx context.Context, workID uint, monetizationID uint) error {
work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(work).Association("Monetizations").Delete(monetization)
}
// ListByBookID finds monetizations by book ID
func (r *monetizationRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Monetization, error) {
var monetizations []domain.Monetization
if err := r.db.WithContext(ctx).Where("book_id = ?", bookID).Find(&monetizations).Error; err != nil {
return nil, err
}
return monetizations, nil
func (r *monetizationRepository) AddMonetizationToAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
author := &domain.Author{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: authorID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(author).Association("Monetizations").Append(monetization)
}
func (r *monetizationRepository) RemoveMonetizationFromAuthor(ctx context.Context, authorID uint, monetizationID uint) error {
author := &domain.Author{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: authorID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(author).Association("Monetizations").Delete(monetization)
}
func (r *monetizationRepository) AddMonetizationToBook(ctx context.Context, bookID uint, monetizationID uint) error {
book := &domain.Book{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: bookID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(book).Association("Monetizations").Append(monetization)
}
func (r *monetizationRepository) RemoveMonetizationFromBook(ctx context.Context, bookID uint, monetizationID uint) error {
book := &domain.Book{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: bookID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(book).Association("Monetizations").Delete(monetization)
}
func (r *monetizationRepository) AddMonetizationToPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
publisher := &domain.Publisher{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: publisherID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(publisher).Association("Monetizations").Append(monetization)
}
func (r *monetizationRepository) RemoveMonetizationFromPublisher(ctx context.Context, publisherID uint, monetizationID uint) error {
publisher := &domain.Publisher{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: publisherID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(publisher).Association("Monetizations").Delete(monetization)
}
func (r *monetizationRepository) AddMonetizationToSource(ctx context.Context, sourceID uint, monetizationID uint) error {
source := &domain.Source{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: sourceID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(source).Association("Monetizations").Append(monetization)
}
func (r *monetizationRepository) RemoveMonetizationFromSource(ctx context.Context, sourceID uint, monetizationID uint) error {
source := &domain.Source{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: sourceID}}}
monetization := &domain.Monetization{BaseModel: domain.BaseModel{ID: monetizationID}}
return r.db.WithContext(ctx).Model(source).Association("Monetizations").Delete(monetization)
}

View File

@ -0,0 +1,44 @@
package sql_test
import (
"context"
"testing"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type MonetizationRepositoryTestSuite struct {
testutil.IntegrationTestSuite
}
func (s *MonetizationRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
}
func (s *MonetizationRepositoryTestSuite) TestAddMonetizationToWork() {
s.Run("should add a monetization to a work", func() {
// Arrange
work := s.CreateTestWork("Test Work", "en", "Test content")
monetization := &domain.Monetization{Amount: 10.0}
s.Require().NoError(s.DB.Create(monetization).Error)
// Act
err := s.MonetizationRepo.AddMonetizationToWork(context.Background(), work.ID, monetization.ID)
// Assert
s.Require().NoError(err)
// Verify that the association was created in the database
var foundWork domain.Work
err = s.DB.Preload("Monetizations").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Require().Len(foundWork.Monetizations, 1)
s.Equal(monetization.ID, foundWork.Monetizations[0].ID)
})
}
func TestMonetizationRepository(t *testing.T) {
suite.Run(t, new(MonetizationRepositoryTestSuite))
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/password_reset"
"time"
"gorm.io/gorm"
@ -16,7 +15,7 @@ type passwordResetRepository struct {
}
// NewPasswordResetRepository creates a new PasswordResetRepository.
func NewPasswordResetRepository(db *gorm.DB) password_reset.Password_resetRepository {
func NewPasswordResetRepository(db *gorm.DB) domain.PasswordResetRepository {
return &passwordResetRepository{
BaseRepository: NewBaseRepositoryImpl[domain.PasswordReset](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"math"
"tercul/internal/domain"
"tercul/internal/domain/place"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type placeRepository struct {
}
// NewPlaceRepository creates a new PlaceRepository.
func NewPlaceRepository(db *gorm.DB) place.PlaceRepository {
func NewPlaceRepository(db *gorm.DB) domain.PlaceRepository {
return &placeRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Place](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/publisher"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type publisherRepository struct {
}
// NewPublisherRepository creates a new PublisherRepository.
func NewPublisherRepository(db *gorm.DB) publisher.PublisherRepository {
func NewPublisherRepository(db *gorm.DB) domain.PublisherRepository {
return &publisherRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Publisher](db),
db: db,

View File

@ -0,0 +1,101 @@
package sql_test
import (
"context"
"testing"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type PublisherRepositoryTestSuite struct {
testutil.IntegrationTestSuite
}
func (s *PublisherRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
}
func (s *PublisherRepositoryTestSuite) TestCreatePublisher() {
s.Run("should create a new publisher", func() {
// Arrange
publisher := &domain.Publisher{
Name: "New Test Publisher",
TranslatableModel: domain.TranslatableModel{
Language: "en",
},
}
// Act
err := s.PublisherRepo.Create(context.Background(), publisher)
// Assert
s.Require().NoError(err)
s.NotZero(publisher.ID)
// Verify that the publisher was actually created in the database
var foundPublisher domain.Publisher
err = s.DB.First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Equal("New Test Publisher", foundPublisher.Name)
s.Equal("en", foundPublisher.Language)
})
}
func (s *PublisherRepositoryTestSuite) TestGetPublisherByID() {
s.Run("should return a publisher by ID", func() {
// Arrange
publisher := &domain.Publisher{Name: "Test Publisher"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
// Act
foundPublisher, err := s.PublisherRepo.GetByID(context.Background(), publisher.ID)
// Assert
s.Require().NoError(err)
s.Require().NotNil(foundPublisher)
s.Equal(publisher.ID, foundPublisher.ID)
s.Equal("Test Publisher", foundPublisher.Name)
})
}
func (s *PublisherRepositoryTestSuite) TestUpdatePublisher() {
s.Run("should update an existing publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "Original Name"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
publisher.Name = "Updated Name"
// Act
err := s.PublisherRepo.Update(context.Background(), publisher)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.First(&foundPublisher, publisher.ID).Error
s.Require().NoError(err)
s.Equal("Updated Name", foundPublisher.Name)
})
}
func (s *PublisherRepositoryTestSuite) TestDeletePublisher() {
s.Run("should delete an existing publisher", func() {
// Arrange
publisher := &domain.Publisher{Name: "To Be Deleted"}
s.Require().NoError(s.PublisherRepo.Create(context.Background(), publisher))
// Act
err := s.PublisherRepo.Delete(context.Background(), publisher.ID)
// Assert
s.Require().NoError(err)
var foundPublisher domain.Publisher
err = s.DB.First(&foundPublisher, publisher.ID).Error
s.Require().Error(err)
})
}
func TestPublisherRepository(t *testing.T) {
suite.Run(t, new(PublisherRepositoryTestSuite))
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/source"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type sourceRepository struct {
}
// NewSourceRepository creates a new SourceRepository.
func NewSourceRepository(db *gorm.DB) source.SourceRepository {
func NewSourceRepository(db *gorm.DB) domain.SourceRepository {
return &sourceRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Source](db),
db: db,

View File

@ -0,0 +1,101 @@
package sql_test
import (
"context"
"testing"
"tercul/internal/domain"
"tercul/internal/testutil"
"github.com/stretchr/testify/suite"
)
type SourceRepositoryTestSuite struct {
testutil.IntegrationTestSuite
}
func (s *SourceRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
}
func (s *SourceRepositoryTestSuite) TestCreateSource() {
s.Run("should create a new source", func() {
// Arrange
source := &domain.Source{
Name: "New Test Source",
TranslatableModel: domain.TranslatableModel{
Language: "en",
},
}
// Act
err := s.SourceRepo.Create(context.Background(), source)
// Assert
s.Require().NoError(err)
s.NotZero(source.ID)
// Verify that the source was actually created in the database
var foundSource domain.Source
err = s.DB.First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Equal("New Test Source", foundSource.Name)
s.Equal("en", foundSource.Language)
})
}
func (s *SourceRepositoryTestSuite) TestGetSourceByID() {
s.Run("should return a source by ID", func() {
// Arrange
source := &domain.Source{Name: "Test Source"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
// Act
foundSource, err := s.SourceRepo.GetByID(context.Background(), source.ID)
// Assert
s.Require().NoError(err)
s.Require().NotNil(foundSource)
s.Equal(source.ID, foundSource.ID)
s.Equal("Test Source", foundSource.Name)
})
}
func (s *SourceRepositoryTestSuite) TestUpdateSource() {
s.Run("should update an existing source", func() {
// Arrange
source := &domain.Source{Name: "Original Name"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
source.Name = "Updated Name"
// Act
err := s.SourceRepo.Update(context.Background(), source)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.First(&foundSource, source.ID).Error
s.Require().NoError(err)
s.Equal("Updated Name", foundSource.Name)
})
}
func (s *SourceRepositoryTestSuite) TestDeleteSource() {
s.Run("should delete an existing source", func() {
// Arrange
source := &domain.Source{Name: "To Be Deleted"}
s.Require().NoError(s.SourceRepo.Create(context.Background(), source))
// Act
err := s.SourceRepo.Delete(context.Background(), source.ID)
// Assert
s.Require().NoError(err)
var foundSource domain.Source
err = s.DB.First(&foundSource, source.ID).Error
s.Require().Error(err)
})
}
func TestSourceRepository(t *testing.T) {
suite.Run(t, new(SourceRepositoryTestSuite))
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/tag"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type tagRepository struct {
}
// NewTagRepository creates a new TagRepository.
func NewTagRepository(db *gorm.DB) tag.TagRepository {
func NewTagRepository(db *gorm.DB) domain.TagRepository {
return &tagRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Tag](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/translation"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type translationRepository struct {
}
// NewTranslationRepository creates a new TranslationRepository.
func NewTranslationRepository(db *gorm.DB) translation.TranslationRepository {
func NewTranslationRepository(db *gorm.DB) domain.TranslationRepository {
return &translationRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Translation](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/user_profile"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type userProfileRepository struct {
}
// NewUserProfileRepository creates a new UserProfileRepository.
func NewUserProfileRepository(db *gorm.DB) user_profile.User_profileRepository {
func NewUserProfileRepository(db *gorm.DB) domain.UserProfileRepository {
return &userProfileRepository{
BaseRepository: NewBaseRepositoryImpl[domain.UserProfile](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/user"
"gorm.io/gorm"
)
@ -15,7 +14,7 @@ type userRepository struct {
}
// NewUserRepository creates a new UserRepository.
func NewUserRepository(db *gorm.DB) user.UserRepository {
func NewUserRepository(db *gorm.DB) domain.UserRepository {
return &userRepository{
BaseRepository: NewBaseRepositoryImpl[domain.User](db),
db: db,

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"tercul/internal/domain"
"tercul/internal/domain/user_session"
"time"
"gorm.io/gorm"
@ -16,7 +15,7 @@ type userSessionRepository struct {
}
// NewUserSessionRepository creates a new UserSessionRepository.
func NewUserSessionRepository(db *gorm.DB) user_session.User_sessionRepository {
func NewUserSessionRepository(db *gorm.DB) domain.UserSessionRepository {
return &userSessionRepository{
BaseRepository: NewBaseRepositoryImpl[domain.UserSession](db),
db: db,

View File

@ -3,7 +3,6 @@ package sql
import (
"context"
"tercul/internal/domain"
"tercul/internal/domain/work"
"gorm.io/gorm"
)
@ -14,7 +13,7 @@ type workRepository struct {
}
// NewWorkRepository creates a new WorkRepository.
func NewWorkRepository(db *gorm.DB) work.WorkRepository {
func NewWorkRepository(db *gorm.DB) domain.WorkRepository {
return &workRepository{
BaseRepository: NewBaseRepositoryImpl[domain.Work](db),
db: db,
@ -100,6 +99,28 @@ func (r *workRepository) FindByLanguage(ctx context.Context, language string, pa
}, 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(&domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: id}}}).Error; err != nil {
return err
}
// Also delete the work itself
if err := tx.Delete(&domain.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) (*domain.Work, error) {
return r.FindWithPreload(ctx, []string{"Translations"}, id)

View File

@ -18,13 +18,20 @@ func (s *WorkRepositoryTestSuite) SetupSuite() {
}
func (s *WorkRepositoryTestSuite) TestCreateWork() {
s.Run("should create a new work", func() {
s.Run("should create a new work with a copyright", func() {
// Arrange
copyright := &domain.Copyright{
Name: "Test Copyright",
Identificator: "TC-123",
}
s.Require().NoError(s.DB.Create(copyright).Error)
work := &domain.Work{
Title: "New Test Work",
TranslatableModel: domain.TranslatableModel{
Language: "en",
},
Copyrights: []*domain.Copyright{copyright},
}
// Act
@ -36,17 +43,26 @@ func (s *WorkRepositoryTestSuite) TestCreateWork() {
// Verify that the work was actually created in the database
var foundWork domain.Work
err = s.DB.First(&foundWork, work.ID).Error
err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Equal("New Test Work", foundWork.Title)
s.Equal("en", foundWork.Language)
s.Require().Len(foundWork.Copyrights, 1)
s.Equal("Test Copyright", foundWork.Copyrights[0].Name)
})
}
func (s *WorkRepositoryTestSuite) TestGetWorkByID() {
s.Run("should return a work by ID", func() {
s.Run("should return a work by ID with copyrights", func() {
// Arrange
copyright := &domain.Copyright{
Name: "Test Copyright",
Identificator: "TC-123",
}
s.Require().NoError(s.DB.Create(copyright).Error)
work := s.CreateTestWork("Test Work", "en", "Test content")
s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright))
// Act
foundWork, err := s.WorkRepo.GetByID(context.Background(), work.ID)
@ -69,10 +85,18 @@ func (s *WorkRepositoryTestSuite) TestGetWorkByID() {
}
func (s *WorkRepositoryTestSuite) TestUpdateWork() {
s.Run("should update an existing work", func() {
s.Run("should update an existing work and its copyrights", func() {
// Arrange
copyright1 := &domain.Copyright{Name: "C1", Identificator: "C1"}
copyright2 := &domain.Copyright{Name: "C2", Identificator: "C2"}
s.Require().NoError(s.DB.Create(&copyright1).Error)
s.Require().NoError(s.DB.Create(&copyright2).Error)
work := s.CreateTestWork("Original Title", "en", "Original content")
s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright1))
work.Title = "Updated Title"
s.Require().NoError(s.DB.Model(work).Association("Copyrights").Replace(copyright2))
// Act
err := s.WorkRepo.Update(context.Background(), work)
@ -82,16 +106,21 @@ func (s *WorkRepositoryTestSuite) TestUpdateWork() {
// Verify that the work was actually updated in the database
var foundWork domain.Work
err = s.DB.First(&foundWork, work.ID).Error
err = s.DB.Preload("Copyrights").First(&foundWork, work.ID).Error
s.Require().NoError(err)
s.Equal("Updated Title", foundWork.Title)
s.Require().Len(foundWork.Copyrights, 1)
s.Equal("C2", foundWork.Copyrights[0].Name)
})
}
func (s *WorkRepositoryTestSuite) TestDeleteWork() {
s.Run("should delete an existing work", func() {
s.Run("should delete an existing work and its associations", func() {
// Arrange
work := s.CreateTestWork("To Be Deleted", "en", "Content")
copyright := &domain.Copyright{Name: "C1", Identificator: "C1"}
s.Require().NoError(s.DB.Create(copyright).Error)
s.Require().NoError(s.DB.Model(work).Association("Copyrights").Append(copyright))
// Act
err := s.WorkRepo.Delete(context.Background(), work.ID)
@ -103,6 +132,11 @@ func (s *WorkRepositoryTestSuite) TestDeleteWork() {
var foundWork domain.Work
err = s.DB.First(&foundWork, work.ID).Error
s.Require().Error(err)
// Verify that the association in the join table is also deleted
var count int64
s.DB.Table("work_copyrights").Where("work_id = ?", work.ID).Count(&count)
s.Zero(count)
})
}

View File

@ -1,15 +0,0 @@
package author
import (
"context"
"tercul/internal/domain"
)
// AuthorRepository defines CRUD methods specific to Author.
type AuthorRepository interface {
domain.BaseRepository[domain.Author]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Author, error)
ListByBookID(ctx context.Context, bookID uint) ([]domain.Author, error)
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Author, error)
}

View File

@ -1,16 +0,0 @@
package book
import (
"context"
"tercul/internal/domain"
)
// BookRepository defines CRUD methods specific to Book.
type BookRepository interface {
domain.BaseRepository[domain.Book]
ListByAuthorID(ctx context.Context, authorID uint) ([]domain.Book, error)
ListByPublisherID(ctx context.Context, publisherID uint) ([]domain.Book, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Book, error)
FindByISBN(ctx context.Context, isbn string) (*domain.Book, error)
}

View File

@ -1,14 +0,0 @@
package bookmark
import (
"context"
"tercul/internal/domain"
)
// BookmarkRepository defines CRUD methods specific to Bookmark.
type BookmarkRepository interface {
domain.BaseRepository[domain.Bookmark]
ListByUserID(ctx context.Context, userID uint) ([]domain.Bookmark, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Bookmark, error)
}

View File

@ -1,15 +0,0 @@
package category
import (
"context"
"tercul/internal/domain"
)
// CategoryRepository defines CRUD methods specific to Category.
type CategoryRepository interface {
domain.BaseRepository[domain.Category]
FindByName(ctx context.Context, name string) (*domain.Category, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Category, error)
ListByParentID(ctx context.Context, parentID *uint) ([]domain.Category, error)
}

View File

@ -1,13 +0,0 @@
package city
import (
"context"
"tercul/internal/domain"
)
// CityRepository defines CRUD methods specific to City.
type CityRepository interface {
domain.BaseRepository[domain.City]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.City, error)
}

View File

@ -1,15 +0,0 @@
package collection
import (
"context"
"tercul/internal/domain"
)
// CollectionRepository defines CRUD methods specific to Collection.
type CollectionRepository interface {
domain.BaseRepository[domain.Collection]
ListByUserID(ctx context.Context, userID uint) ([]domain.Collection, error)
ListPublic(ctx context.Context) ([]domain.Collection, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Collection, error)
}

View File

@ -1,16 +0,0 @@
package comment
import (
"context"
"tercul/internal/domain"
)
// CommentRepository defines CRUD methods specific to Comment.
type CommentRepository interface {
domain.BaseRepository[domain.Comment]
ListByUserID(ctx context.Context, userID uint) ([]domain.Comment, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Comment, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Comment, error)
ListByParentID(ctx context.Context, parentID uint) ([]domain.Comment, error)
}

View File

@ -1,17 +0,0 @@
package contribution
import (
"context"
"tercul/internal/domain"
)
// ContributionRepository defines CRUD methods specific to Contribution.
type ContributionRepository interface {
domain.BaseRepository[domain.Contribution]
ListByUserID(ctx context.Context, userID uint) ([]domain.Contribution, error)
ListByReviewerID(ctx context.Context, reviewerID uint) ([]domain.Contribution, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Contribution, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Contribution, error)
ListByStatus(ctx context.Context, status string) ([]domain.Contribution, error)
}

View File

@ -1,19 +0,0 @@
package copyright
import (
"context"
"tercul/internal/domain"
)
// CopyrightRepository defines CRUD methods specific to Copyright.
type CopyrightRepository interface {
domain.BaseRepository[domain.Copyright]
AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) (error)
DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) (error)
GetByEntity(ctx context.Context, entityID uint, entityType string) ([]domain.Copyright, error)
GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]domain.Copyrightable, error)
AddTranslation(ctx context.Context, translation *domain.CopyrightTranslation) (error)
GetTranslations(ctx context.Context, copyrightID uint) ([]domain.CopyrightTranslation, error)
GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*domain.CopyrightTranslation, error)
}

View File

@ -1,14 +0,0 @@
package copyright_claim
import (
"context"
"tercul/internal/domain"
)
// Copyright_claimRepository defines CRUD methods specific to Copyright_claim.
type Copyright_claimRepository interface {
domain.BaseRepository[domain.CopyrightClaim]
ListByWorkID(ctx context.Context, workID uint) ([]domain.CopyrightClaim, error)
ListByUserID(ctx context.Context, userID uint) ([]domain.CopyrightClaim, error)
}

View File

@ -1,14 +0,0 @@
package country
import (
"context"
"tercul/internal/domain"
)
// CountryRepository defines CRUD methods specific to Country.
type CountryRepository interface {
domain.BaseRepository[domain.Country]
GetByCode(ctx context.Context, code string) (*domain.Country, error)
ListByContinent(ctx context.Context, continent string) ([]domain.Country, error)
}

View File

@ -1,13 +0,0 @@
package edge
import (
"context"
"tercul/internal/domain"
)
// EdgeRepository defines CRUD methods specific to Edge.
type EdgeRepository interface {
domain.BaseRepository[domain.Edge]
ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]domain.Edge, error)
}

View File

@ -1,14 +0,0 @@
package edition
import (
"context"
"tercul/internal/domain"
)
// EditionRepository defines CRUD methods specific to Edition.
type EditionRepository interface {
domain.BaseRepository[domain.Edition]
ListByBookID(ctx context.Context, bookID uint) ([]domain.Edition, error)
FindByISBN(ctx context.Context, isbn string) (*domain.Edition, error)
}

View File

@ -1,16 +0,0 @@
package email_verification
import (
"context"
"tercul/internal/domain"
)
// Email_verificationRepository defines CRUD methods specific to Email_verification.
type Email_verificationRepository interface {
domain.BaseRepository[domain.EmailVerification]
GetByToken(ctx context.Context, token string) (*domain.EmailVerification, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.EmailVerification, error)
DeleteExpired(ctx context.Context) (error)
MarkAsUsed(ctx context.Context, id uint) (error)
}

View File

@ -206,8 +206,8 @@ type Work struct {
Authors []*Author `gorm:"many2many:work_authors"`
Tags []*Tag `gorm:"many2many:work_tags"`
Categories []*Category `gorm:"many2many:work_categories"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
Copyrights []*Copyright `gorm:"many2many:work_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:work_monetizations;constraint:OnDelete:CASCADE"`
}
type AuthorStatus string
@ -233,8 +233,8 @@ type Author struct {
AddressID *uint
Address *Address `gorm:"foreignKey:AddressID"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
Copyrights []*Copyright `gorm:"many2many:author_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:author_monetizations;constraint:OnDelete:CASCADE"`
}
type BookStatus string
@ -265,8 +265,8 @@ type Book struct {
PublisherID *uint
Publisher *Publisher `gorm:"foreignKey:PublisherID"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
Copyrights []*Copyright `gorm:"many2many:book_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:book_monetizations;constraint:OnDelete:CASCADE"`
}
type PublisherStatus string
@ -284,8 +284,8 @@ type Publisher struct {
CountryID *uint
Country *Country `gorm:"foreignKey:CountryID"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
Copyrights []*Copyright `gorm:"many2many:publisher_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:publisher_monetizations;constraint:OnDelete:CASCADE"`
}
type SourceStatus string
@ -302,8 +302,8 @@ type Source struct {
Status SourceStatus `gorm:"size:50;default:'active'"`
Works []*Work `gorm:"many2many:work_sources"`
Translations []Translation `gorm:"polymorphic:Translatable"`
Copyrights []Copyright `gorm:"-"`
Monetizations []Monetization `gorm:"-"`
Copyrights []*Copyright `gorm:"many2many:source_copyrights;constraint:OnDelete:CASCADE"`
Monetizations []*Monetization `gorm:"many2many:source_monetizations;constraint:OnDelete:CASCADE"`
}
type EditionStatus string
@ -574,16 +574,47 @@ type Copyright struct {
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 WorkCopyright struct {
WorkID uint `gorm:"primaryKey;index"`
CopyrightID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (WorkCopyright) TableName() string { return "work_copyrights" }
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
@ -607,7 +638,6 @@ type CopyrightClaim struct {
ResolvedAt *time.Time
UserID *uint
User *User `gorm:"foreignKey:UserID"`
Claimables []Copyrightable `gorm:"-"`
}
type MonetizationType string
const (
@ -623,13 +653,45 @@ const (
MonetizationStatusInactive MonetizationStatus = "inactive"
MonetizationStatusPending MonetizationStatus = "pending"
)
type Monetizable struct {
BaseModel
MonetizationID uint
Monetization *Monetization `gorm:"foreignKey:MonetizationID"`
MonetizableID uint
MonetizableType string
type WorkMonetization struct {
WorkID uint `gorm:"primaryKey;index"`
MonetizationID uint `gorm:"primaryKey;index"`
CreatedAt time.Time
}
func (WorkMonetization) TableName() string { return "work_monetizations" }
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"`
@ -639,7 +701,6 @@ type Monetization struct {
StartDate *time.Time
EndDate *time.Time
Language string `gorm:"size:50;not null"`
Monetizables []Monetizable `gorm:"-"`
}
type License struct {
BaseModel

View File

@ -16,6 +16,191 @@ type PaginatedResult[T any] struct {
HasPrev bool `json:"hasPrev"`
}
// MonetizationRepository defines CRUD methods specific to Monetization.
type MonetizationRepository interface {
BaseRepository[Monetization]
AddMonetizationToWork(ctx context.Context, workID uint, monetizationID uint) error
RemoveMonetizationFromWork(ctx context.Context, workID uint, monetizationID uint) error
AddMonetizationToAuthor(ctx context.Context, authorID uint, monetizationID uint) error
RemoveMonetizationFromAuthor(ctx context.Context, authorID uint, monetizationID uint) error
AddMonetizationToBook(ctx context.Context, bookID uint, monetizationID uint) error
RemoveMonetizationFromBook(ctx context.Context, bookID uint, monetizationID uint) error
AddMonetizationToPublisher(ctx context.Context, publisherID uint, monetizationID uint) error
RemoveMonetizationFromPublisher(ctx context.Context, publisherID uint, monetizationID uint) error
AddMonetizationToSource(ctx context.Context, sourceID uint, monetizationID uint) error
RemoveMonetizationFromSource(ctx context.Context, sourceID uint, monetizationID uint) error
}
// PublisherRepository defines CRUD methods specific to Publisher.
type PublisherRepository interface {
BaseRepository[Publisher]
ListByCountryID(ctx context.Context, countryID uint) ([]Publisher, error)
}
// SourceRepository defines CRUD methods specific to Source.
type SourceRepository interface {
BaseRepository[Source]
ListByWorkID(ctx context.Context, workID uint) ([]Source, error)
FindByURL(ctx context.Context, url string) (*Source, error)
}
// BookRepository defines CRUD methods specific to Book.
type BookRepository interface {
BaseRepository[Book]
ListByAuthorID(ctx context.Context, authorID uint) ([]Book, error)
ListByPublisherID(ctx context.Context, publisherID uint) ([]Book, error)
ListByWorkID(ctx context.Context, workID uint) ([]Book, error)
FindByISBN(ctx context.Context, isbn string) (*Book, error)
}
// BookmarkRepository defines CRUD methods specific to Bookmark.
type BookmarkRepository interface {
BaseRepository[Bookmark]
ListByUserID(ctx context.Context, userID uint) ([]Bookmark, error)
ListByWorkID(ctx context.Context, workID uint) ([]Bookmark, error)
}
// CategoryRepository defines CRUD methods specific to Category.
type CategoryRepository interface {
BaseRepository[Category]
FindByName(ctx context.Context, name string) (*Category, error)
ListByWorkID(ctx context.Context, workID uint) ([]Category, error)
ListByParentID(ctx context.Context, parentID *uint) ([]Category, error)
}
// CityRepository defines CRUD methods specific to City.
type CityRepository interface {
BaseRepository[City]
ListByCountryID(ctx context.Context, countryID uint) ([]City, error)
}
// CollectionRepository defines CRUD methods specific to Collection.
type CollectionRepository interface {
BaseRepository[Collection]
ListByUserID(ctx context.Context, userID uint) ([]Collection, error)
ListPublic(ctx context.Context) ([]Collection, error)
ListByWorkID(ctx context.Context, workID uint) ([]Collection, error)
}
// CommentRepository defines CRUD methods specific to Comment.
type CommentRepository interface {
BaseRepository[Comment]
ListByUserID(ctx context.Context, userID uint) ([]Comment, error)
ListByWorkID(ctx context.Context, workID uint) ([]Comment, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]Comment, error)
ListByParentID(ctx context.Context, parentID uint) ([]Comment, error)
}
// ContributionRepository defines CRUD methods specific to Contribution.
type ContributionRepository interface {
BaseRepository[Contribution]
ListByUserID(ctx context.Context, userID uint) ([]Contribution, error)
ListByReviewerID(ctx context.Context, reviewerID uint) ([]Contribution, error)
ListByWorkID(ctx context.Context, workID uint) ([]Contribution, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]Contribution, error)
ListByStatus(ctx context.Context, status string) ([]Contribution, error)
}
// CopyrightClaimRepository defines CRUD methods specific to CopyrightClaim.
type CopyrightClaimRepository interface {
BaseRepository[CopyrightClaim]
ListByWorkID(ctx context.Context, workID uint) ([]CopyrightClaim, error)
ListByUserID(ctx context.Context, userID uint) ([]CopyrightClaim, error)
}
// CountryRepository defines CRUD methods specific to Country.
type CountryRepository interface {
BaseRepository[Country]
GetByCode(ctx context.Context, code string) (*Country, error)
ListByContinent(ctx context.Context, continent string) ([]Country, error)
}
// EdgeRepository defines CRUD methods specific to Edge.
type EdgeRepository interface {
BaseRepository[Edge]
ListBySource(ctx context.Context, sourceTable string, sourceID uint) ([]Edge, error)
}
// EditionRepository defines CRUD methods specific to Edition.
type EditionRepository interface {
BaseRepository[Edition]
ListByBookID(ctx context.Context, bookID uint) ([]Edition, error)
FindByISBN(ctx context.Context, isbn string) (*Edition, error)
}
// EmailVerificationRepository defines CRUD methods specific to EmailVerification.
type EmailVerificationRepository interface {
BaseRepository[EmailVerification]
GetByToken(ctx context.Context, token string) (*EmailVerification, error)
GetByUserID(ctx context.Context, userID uint) ([]EmailVerification, error)
DeleteExpired(ctx context.Context) error
MarkAsUsed(ctx context.Context, id uint) error
}
// LikeRepository defines CRUD methods specific to Like.
type LikeRepository interface {
BaseRepository[Like]
ListByUserID(ctx context.Context, userID uint) ([]Like, error)
ListByWorkID(ctx context.Context, workID uint) ([]Like, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]Like, error)
ListByCommentID(ctx context.Context, commentID uint) ([]Like, error)
}
// PasswordResetRepository defines CRUD methods specific to PasswordReset.
type PasswordResetRepository interface {
BaseRepository[PasswordReset]
GetByToken(ctx context.Context, token string) (*PasswordReset, error)
GetByUserID(ctx context.Context, userID uint) ([]PasswordReset, error)
DeleteExpired(ctx context.Context) error
MarkAsUsed(ctx context.Context, id uint) error
}
// PlaceRepository defines CRUD methods specific to Place.
type PlaceRepository interface {
BaseRepository[Place]
ListByCountryID(ctx context.Context, countryID uint) ([]Place, error)
ListByCityID(ctx context.Context, cityID uint) ([]Place, error)
FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]Place, error)
}
// TagRepository defines CRUD methods specific to Tag.
type TagRepository interface {
BaseRepository[Tag]
FindByName(ctx context.Context, name string) (*Tag, error)
ListByWorkID(ctx context.Context, workID uint) ([]Tag, error)
}
// TranslationRepository defines CRUD methods specific to Translation.
type TranslationRepository interface {
BaseRepository[Translation]
ListByWorkID(ctx context.Context, workID uint) ([]Translation, error)
ListByEntity(ctx context.Context, entityType string, entityID uint) ([]Translation, error)
ListByTranslatorID(ctx context.Context, translatorID uint) ([]Translation, error)
ListByStatus(ctx context.Context, status TranslationStatus) ([]Translation, error)
}
// UserRepository defines CRUD methods specific to User.
type UserRepository interface {
BaseRepository[User]
FindByUsername(ctx context.Context, username string) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
ListByRole(ctx context.Context, role UserRole) ([]User, error)
}
// UserProfileRepository defines CRUD methods specific to UserProfile.
type UserProfileRepository interface {
BaseRepository[UserProfile]
GetByUserID(ctx context.Context, userID uint) (*UserProfile, error)
}
// UserSessionRepository defines CRUD methods specific to UserSession.
type UserSessionRepository interface {
BaseRepository[UserSession]
GetByToken(ctx context.Context, token string) (*UserSession, error)
GetByUserID(ctx context.Context, userID uint) ([]UserSession, error)
DeleteExpired(ctx context.Context) error
}
// QueryOptions provides options for repository queries
type QueryOptions struct {
Preloads []string
@ -66,87 +251,20 @@ type AuthorRepository interface {
ListByCountryID(ctx context.Context, countryID uint) ([]Author, error)
}
// BookRepository defines CRUD methods specific to Book.
type BookRepository interface {
BaseRepository[Book]
ListByAuthorID(ctx context.Context, authorID uint) ([]Book, error)
ListByPublisherID(ctx context.Context, publisherID uint) ([]Book, error)
ListByWorkID(ctx context.Context, workID uint) ([]Book, error)
FindByISBN(ctx context.Context, isbn string) (*Book, error)
}
// UserRepository defines CRUD methods specific to User.
type UserRepository interface {
BaseRepository[User]
FindByUsername(ctx context.Context, username string) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
ListByRole(ctx context.Context, role UserRole) ([]User, error)
}
// TranslationRepository defines CRUD methods specific to Translation.
type TranslationRepository interface {
BaseRepository[Translation]
ListByWorkID(ctx context.Context, workID uint) ([]Translation, error)
ListByEntity(ctx context.Context, entityType string, entityID uint) ([]Translation, error)
ListByTranslatorID(ctx context.Context, translatorID uint) ([]Translation, error)
ListByStatus(ctx context.Context, status TranslationStatus) ([]Translation, error)
}
// CommentRepository defines CRUD methods specific to Comment.
type CommentRepository interface {
BaseRepository[Comment]
ListByUserID(ctx context.Context, userID uint) ([]Comment, error)
ListByWorkID(ctx context.Context, workID uint) ([]Comment, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]Comment, error)
ListByParentID(ctx context.Context, parentID uint) ([]Comment, error)
}
// LikeRepository defines CRUD methods specific to Like.
type LikeRepository interface {
BaseRepository[Like]
ListByUserID(ctx context.Context, userID uint) ([]Like, error)
ListByWorkID(ctx context.Context, workID uint) ([]Like, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]Like, error)
ListByCommentID(ctx context.Context, commentID uint) ([]Like, error)
}
// BookmarkRepository defines CRUD methods specific to Bookmark.
type BookmarkRepository interface {
BaseRepository[Bookmark]
ListByUserID(ctx context.Context, userID uint) ([]Bookmark, error)
ListByWorkID(ctx context.Context, workID uint) ([]Bookmark, error)
}
// CollectionRepository defines CRUD methods specific to Collection.
type CollectionRepository interface {
BaseRepository[Collection]
ListByUserID(ctx context.Context, userID uint) ([]Collection, error)
ListPublic(ctx context.Context) ([]Collection, error)
ListByWorkID(ctx context.Context, workID uint) ([]Collection, error)
}
// TagRepository defines CRUD methods specific to Tag.
type TagRepository interface {
BaseRepository[Tag]
FindByName(ctx context.Context, name string) (*Tag, error)
ListByWorkID(ctx context.Context, workID uint) ([]Tag, error)
}
// CategoryRepository defines CRUD methods specific to Category.
type CategoryRepository interface {
BaseRepository[Category]
FindByName(ctx context.Context, name string) (*Category, error)
ListByWorkID(ctx context.Context, workID uint) ([]Category, error)
ListByParentID(ctx context.Context, parentID *uint) ([]Category, error)
}
// CopyrightRepository defines CRUD methods specific to Copyright.
type CopyrightRepository interface {
BaseRepository[Copyright]
AttachToEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
DetachFromEntity(ctx context.Context, copyrightID uint, entityID uint, entityType string) error
GetByEntity(ctx context.Context, entityID uint, entityType string) ([]Copyright, error)
GetEntitiesByCopyright(ctx context.Context, copyrightID uint) ([]Copyrightable, error)
AddCopyrightToWork(ctx context.Context, workID uint, copyrightID uint) error
RemoveCopyrightFromWork(ctx context.Context, workID uint, copyrightID uint) error
AddCopyrightToAuthor(ctx context.Context, authorID uint, copyrightID uint) error
RemoveCopyrightFromAuthor(ctx context.Context, authorID uint, copyrightID uint) error
AddCopyrightToBook(ctx context.Context, bookID uint, copyrightID uint) error
RemoveCopyrightFromBook(ctx context.Context, bookID uint, copyrightID uint) error
AddCopyrightToPublisher(ctx context.Context, publisherID uint, copyrightID uint) error
RemoveCopyrightFromPublisher(ctx context.Context, publisherID uint, copyrightID uint) error
AddCopyrightToSource(ctx context.Context, sourceID uint, copyrightID uint) error
RemoveCopyrightFromSource(ctx context.Context, sourceID uint, copyrightID uint) error
AddTranslation(ctx context.Context, translation *CopyrightTranslation) error
GetTranslations(ctx context.Context, copyrightID uint) ([]CopyrightTranslation, error)
GetTranslationByLanguage(ctx context.Context, copyrightID uint, languageCode string) (*CopyrightTranslation, error)

View File

@ -1,16 +0,0 @@
package like
import (
"context"
"tercul/internal/domain"
)
// LikeRepository defines CRUD methods specific to Like.
type LikeRepository interface {
domain.BaseRepository[domain.Like]
ListByUserID(ctx context.Context, userID uint) ([]domain.Like, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Like, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Like, error)
ListByCommentID(ctx context.Context, commentID uint) ([]domain.Like, error)
}

View File

@ -1,15 +0,0 @@
package monetization
import (
"context"
"tercul/internal/domain"
)
// MonetizationRepository defines CRUD methods specific to Monetization.
type MonetizationRepository interface {
domain.BaseRepository[domain.Monetization]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Monetization, error)
ListByTranslationID(ctx context.Context, translationID uint) ([]domain.Monetization, error)
ListByBookID(ctx context.Context, bookID uint) ([]domain.Monetization, error)
}

View File

@ -1,16 +0,0 @@
package password_reset
import (
"context"
"tercul/internal/domain"
)
// Password_resetRepository defines CRUD methods specific to Password_reset.
type Password_resetRepository interface {
domain.BaseRepository[domain.PasswordReset]
GetByToken(ctx context.Context, token string) (*domain.PasswordReset, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.PasswordReset, error)
DeleteExpired(ctx context.Context) (error)
MarkAsUsed(ctx context.Context, id uint) (error)
}

View File

@ -1,15 +0,0 @@
package place
import (
"context"
"tercul/internal/domain"
)
// PlaceRepository defines CRUD methods specific to Place.
type PlaceRepository interface {
domain.BaseRepository[domain.Place]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Place, error)
ListByCityID(ctx context.Context, cityID uint) ([]domain.Place, error)
FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]domain.Place, error)
}

View File

@ -1,13 +0,0 @@
package publisher
import (
"context"
"tercul/internal/domain"
)
// PublisherRepository defines CRUD methods specific to Publisher.
type PublisherRepository interface {
domain.BaseRepository[domain.Publisher]
ListByCountryID(ctx context.Context, countryID uint) ([]domain.Publisher, error)
}

View File

@ -1,14 +0,0 @@
package source
import (
"context"
"tercul/internal/domain"
)
// SourceRepository defines CRUD methods specific to Source.
type SourceRepository interface {
domain.BaseRepository[domain.Source]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Source, error)
FindByURL(ctx context.Context, url string) (*domain.Source, error)
}

View File

@ -1,14 +0,0 @@
package tag
import (
"context"
"tercul/internal/domain"
)
// TagRepository defines CRUD methods specific to Tag.
type TagRepository interface {
domain.BaseRepository[domain.Tag]
FindByName(ctx context.Context, name string) (*domain.Tag, error)
ListByWorkID(ctx context.Context, workID uint) ([]domain.Tag, error)
}

View File

@ -1,16 +0,0 @@
package translation
import (
"context"
"tercul/internal/domain"
)
// TranslationRepository defines CRUD methods specific to Translation.
type TranslationRepository interface {
domain.BaseRepository[domain.Translation]
ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error)
ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error)
ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error)
ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error)
}

View File

@ -1,15 +0,0 @@
package user
import (
"context"
"tercul/internal/domain"
)
// UserRepository defines CRUD methods specific to User.
type UserRepository interface {
domain.BaseRepository[domain.User]
FindByUsername(ctx context.Context, username string) (*domain.User, error)
FindByEmail(ctx context.Context, email string) (*domain.User, error)
ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error)
}

View File

@ -1,13 +0,0 @@
package user_profile
import (
"context"
"tercul/internal/domain"
)
// User_profileRepository defines CRUD methods specific to User_profile.
type User_profileRepository interface {
domain.BaseRepository[domain.UserProfile]
GetByUserID(ctx context.Context, userID uint) (*domain.UserProfile, error)
}

View File

@ -1,15 +0,0 @@
package user_session
import (
"context"
"tercul/internal/domain"
)
// User_sessionRepository defines CRUD methods specific to User_session.
type User_sessionRepository interface {
domain.BaseRepository[domain.UserSession]
GetByToken(ctx context.Context, token string) (*domain.UserSession, error)
GetByUserID(ctx context.Context, userID uint) ([]domain.UserSession, error)
DeleteExpired(ctx context.Context) (error)
}

View File

@ -1,18 +0,0 @@
package work
import (
"context"
"tercul/internal/domain"
)
// WorkRepository defines CRUD methods specific to Work.
type WorkRepository interface {
domain.BaseRepository[domain.Work]
FindByTitle(ctx context.Context, title string) ([]domain.Work, error)
FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error)
FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error)
FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error)
GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error)
ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error)
}

View File

@ -35,6 +35,11 @@ type IntegrationTestSuite struct {
CollectionRepo domain.CollectionRepository
TagRepo domain.TagRepository
CategoryRepo domain.CategoryRepository
BookRepo domain.BookRepository
MonetizationRepo domain.MonetizationRepository
PublisherRepo domain.PublisherRepository
SourceRepo domain.SourceRepository
CopyrightRepo domain.CopyrightRepository
// Services
WorkCommands *work.WorkCommands
@ -139,6 +144,16 @@ func (s *IntegrationTestSuite) setupInMemoryDB(config *TestConfig) {
&domain.Book{},
&domain.Publisher{},
&domain.Source{},
&domain.WorkCopyright{},
&domain.AuthorCopyright{},
&domain.BookCopyright{},
&domain.PublisherCopyright{},
&domain.SourceCopyright{},
&domain.WorkMonetization{},
&domain.AuthorMonetization{},
&domain.BookMonetization{},
&domain.PublisherMonetization{},
&domain.SourceMonetization{},
// &domain.WorkAnalytics{}, // Commented out as it's not in models package
&domain.ReadabilityScore{},
&domain.WritingStyle{},
@ -166,6 +181,11 @@ func (s *IntegrationTestSuite) setupInMemoryDB(config *TestConfig) {
s.CollectionRepo = sql.NewCollectionRepository(db)
s.TagRepo = sql.NewTagRepository(db)
s.CategoryRepo = sql.NewCategoryRepository(db)
s.BookRepo = sql.NewBookRepository(db)
s.MonetizationRepo = sql.NewMonetizationRepository(db)
s.PublisherRepo = sql.NewPublisherRepository(db)
s.SourceRepo = sql.NewSourceRepository(db)
s.CopyrightRepo = sql.NewCopyrightRepository(db)
}
// setupMockRepositories sets up mock repositories for testing