package services import ( "context" "math" "github.com/fish/language/backend/internal/models" "github.com/fish/language/backend/internal/repository/queries" ) const defaultPageSize = 20 const maxPageSize = 100 // WordService handles business logic for words. type WordService struct { q *queries.Queries } // NewWordService creates a new WordService. func NewWordService(q *queries.Queries) *WordService { return &WordService{q: q} } // List returns a paginated list of words with optional filters. func (s *WordService) List(ctx context.Context, req models.ListWordsRequest) (models.ListWordsResponse, error) { page := req.Page if page < 1 { page = 1 } pageSize := req.PageSize if pageSize < 1 { pageSize = defaultPageSize } if pageSize > maxPageSize { pageSize = maxPageSize } offset := (page - 1) * pageSize listParams := queries.ListWordsParams{ Limit: pageSize, Offset: offset, CategoryID: 0, Keyword: "", } countParams := queries.CountWordsParams{ CategoryID: 0, Keyword: "", } if req.CategoryID != nil && *req.CategoryID > 0 { listParams.CategoryID = *req.CategoryID countParams.CategoryID = *req.CategoryID } if req.Keyword != "" { listParams.Keyword = req.Keyword countParams.Keyword = req.Keyword } rows, err := s.q.ListWords(ctx, listParams) if err != nil { return models.ListWordsResponse{}, err } total, err := s.q.CountWords(ctx, countParams) if err != nil { return models.ListWordsResponse{}, err } words := make([]models.Word, len(rows)) for i, r := range rows { words[i] = toWord(r) } totalPages := int32(math.Ceil(float64(total) / float64(pageSize))) return models.ListWordsResponse{ Words: words, Total: total, Page: page, PageSize: pageSize, TotalPages: totalPages, }, nil } // Get returns a single word by id. func (s *WordService) Get(ctx context.Context, id int32) (models.Word, error) { row, err := s.q.GetWord(ctx, id) if err != nil { return models.Word{}, err } return toWord(row), nil } // Create creates a new word. func (s *WordService) Create(ctx context.Context, req models.CreateWordRequest) (models.Word, error) { row, err := s.q.CreateWord(ctx, queries.CreateWordParams{ CategoryID: req.CategoryID, Word: req.Word, Translation: req.Translation, Phonetic: req.Phonetic, ExampleSentence: req.ExampleSentence, AudioUrl: req.AudioURL, ImageUrl: req.ImageURL, SortOrder: req.SortOrder, }) if err != nil { return models.Word{}, err } return toWord(row), nil } // Update updates an existing word. func (s *WordService) Update(ctx context.Context, id int32, req models.UpdateWordRequest) (models.Word, error) { row, err := s.q.UpdateWord(ctx, queries.UpdateWordParams{ ID: id, CategoryID: req.CategoryID, Word: req.Word, Translation: req.Translation, Phonetic: req.Phonetic, ExampleSentence: req.ExampleSentence, AudioUrl: req.AudioURL, ImageUrl: req.ImageURL, SortOrder: req.SortOrder, }) if err != nil { return models.Word{}, err } return toWord(row), nil } // Delete removes a word by id. func (s *WordService) Delete(ctx context.Context, id int32) error { return s.q.DeleteWord(ctx, id) } func toWord(w queries.Word) models.Word { return models.Word{ ID: w.ID, CategoryID: w.CategoryID, Word: w.Word, Translation: w.Translation, Phonetic: nullString(w.Phonetic), ExampleSentence: nullString(w.ExampleSentence), AudioURL: nullString(w.AudioUrl), ImageURL: nullString(w.ImageUrl), SortOrder: w.SortOrder, CreatedAt: w.CreatedAt, UpdatedAt: w.UpdatedAt, } } func nullString(s *string) *string { if s == nil { return nil } v := *s return &v }