package repositories import ( "context" "time" "tercul/cache" "tercul/logger" "tercul/models" ) // CachedWorkRepository wraps a WorkRepository with caching functionality type CachedWorkRepository struct { *CachedRepository[models.Work] workRepo WorkRepository } // NewCachedWorkRepository creates a new CachedWorkRepository func NewCachedWorkRepository( workRepo WorkRepository, cache cache.Cache, keyGenerator cache.KeyGenerator, cacheExpiry time.Duration, ) *CachedWorkRepository { if keyGenerator == nil { keyGenerator = &simpleKeyGenerator{prefix: "tercul:"} } if cacheExpiry == 0 { cacheExpiry = 30 * time.Minute // Default expiry of 30 minutes } return &CachedWorkRepository{ CachedRepository: NewCachedRepository[models.Work]( workRepo, cache, keyGenerator, "work", cacheExpiry, ), workRepo: workRepo, } } // FindByTitle finds works by title (partial match) func (r *CachedWorkRepository) FindByTitle(ctx context.Context, title string) ([]models.Work, error) { if !r.cacheEnabled { return r.workRepo.FindByTitle(ctx, title) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "title", title) var result []models.Work err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for FindByTitle", logger.F("entityType", r.entityType), logger.F("title", title)) return result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for FindByTitle", logger.F("entityType", r.entityType), logger.F("title", title)) result, err = r.workRepo.FindByTitle(ctx, title) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache FindByTitle result", logger.F("entityType", r.entityType), logger.F("title", title), logger.F("error", err)) } return result, nil } // FindByAuthor finds works by author ID func (r *CachedWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]models.Work, error) { if !r.cacheEnabled { return r.workRepo.FindByAuthor(ctx, authorID) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "author", authorID) var result []models.Work err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for FindByAuthor", logger.F("entityType", r.entityType), logger.F("authorID", authorID)) return result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for FindByAuthor", logger.F("entityType", r.entityType), logger.F("authorID", authorID)) result, err = r.workRepo.FindByAuthor(ctx, authorID) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache FindByAuthor result", logger.F("entityType", r.entityType), logger.F("authorID", authorID), logger.F("error", err)) } return result, nil } // FindByCategory finds works by category ID func (r *CachedWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]models.Work, error) { if !r.cacheEnabled { return r.workRepo.FindByCategory(ctx, categoryID) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "category", categoryID) var result []models.Work err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for FindByCategory", logger.F("entityType", r.entityType), logger.F("categoryID", categoryID)) return result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for FindByCategory", logger.F("entityType", r.entityType), logger.F("categoryID", categoryID)) result, err = r.workRepo.FindByCategory(ctx, categoryID) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache FindByCategory result", logger.F("entityType", r.entityType), logger.F("categoryID", categoryID), logger.F("error", err)) } return result, nil } // FindByLanguage finds works by language with pagination func (r *CachedWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*PaginatedResult[models.Work], error) { if !r.cacheEnabled { return r.workRepo.FindByLanguage(ctx, language, page, pageSize) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "language", language, page, pageSize) var result PaginatedResult[models.Work] err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for FindByLanguage", logger.F("entityType", r.entityType), logger.F("language", language), logger.F("page", page), logger.F("pageSize", pageSize)) return &result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for FindByLanguage", logger.F("entityType", r.entityType), logger.F("language", language), logger.F("page", page), logger.F("pageSize", pageSize)) result_ptr, err := r.workRepo.FindByLanguage(ctx, language, page, pageSize) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result_ptr, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache FindByLanguage result", logger.F("entityType", r.entityType), logger.F("language", language), logger.F("page", page), logger.F("pageSize", pageSize), logger.F("error", err)) } return result_ptr, nil } // GetWithTranslations gets a work with its translations func (r *CachedWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*models.Work, error) { if !r.cacheEnabled { return r.workRepo.GetWithTranslations(ctx, id) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "with_translations", id) var result models.Work err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for GetWithTranslations", logger.F("entityType", r.entityType), logger.F("id", id)) return &result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for GetWithTranslations", logger.F("entityType", r.entityType), logger.F("id", id)) result_ptr, err := r.workRepo.GetWithTranslations(ctx, id) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result_ptr, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache GetWithTranslations result", logger.F("entityType", r.entityType), logger.F("id", id), logger.F("error", err)) } return result_ptr, nil } // ListWithTranslations lists works with their translations func (r *CachedWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*PaginatedResult[models.Work], error) { if !r.cacheEnabled { return r.workRepo.ListWithTranslations(ctx, page, pageSize) } cacheKey := r.keyGenerator.QueryKey(r.entityType, "list_with_translations", page, pageSize) var result PaginatedResult[models.Work] err := r.cache.Get(ctx, cacheKey, &result) if err == nil { // Cache hit logger.LogDebug("Cache hit for ListWithTranslations", logger.F("entityType", r.entityType), logger.F("page", page), logger.F("pageSize", pageSize)) return &result, nil } // Cache miss, get from database logger.LogDebug("Cache miss for ListWithTranslations", logger.F("entityType", r.entityType), logger.F("page", page), logger.F("pageSize", pageSize)) result_ptr, err := r.workRepo.ListWithTranslations(ctx, page, pageSize) if err != nil { return nil, err } // Store in cache if err := r.cache.Set(ctx, cacheKey, result_ptr, r.cacheExpiry); err != nil { logger.LogWarn("Failed to cache ListWithTranslations result", logger.F("entityType", r.entityType), logger.F("page", page), logger.F("pageSize", pageSize), logger.F("error", err)) } return result_ptr, nil }