tercul-backend/repositories/cached_work_repository.go
Damir Mukimov 4957117cb6 Initial commit: Tercul Go project with comprehensive architecture
- Core Go application with GraphQL API using gqlgen
- Comprehensive data models for literary works, authors, translations
- Repository pattern with caching layer
- Authentication and authorization system
- Linguistics analysis capabilities with multiple adapters
- Vector search integration with Weaviate
- Docker containerization support
- Python data migration and analysis scripts
- Clean architecture with proper separation of concerns
- Production-ready configuration and middleware
- Proper .gitignore excluding vendor/, database files, and build artifacts
2025-08-13 07:42:32 +02:00

287 lines
7.7 KiB
Go

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
}