Yimaru-BackEnd/internal/services/faqs/service.go
Yared Yemane 6a4fe68628 Add full FAQ management APIs and integration assets.
Implement public FAQ read endpoints and admin CRUD with RBAC, persistence, and migrations, then regenerate Swagger and add a complete Postman collection so frontend/admin teams can integrate and validate the feature end-to-end.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 07:58:17 -07:00

130 lines
3.1 KiB
Go

package faqs
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"context"
"fmt"
"strings"
)
type Service struct {
faqStore ports.FAQStore
}
func NewService(faqStore ports.FAQStore) *Service {
return &Service{faqStore: faqStore}
}
func normalizeFAQStatus(status *string) (string, error) {
if status == nil || strings.TrimSpace(*status) == "" {
return domain.FAQStatusActive, nil
}
value := strings.ToUpper(strings.TrimSpace(*status))
switch value {
case domain.FAQStatusActive, domain.FAQStatusInactive:
return value, nil
default:
return "", fmt.Errorf("status must be one of %s, %s", domain.FAQStatusActive, domain.FAQStatusInactive)
}
}
func (s *Service) CreateFAQ(ctx context.Context, input domain.CreateFAQInput) (domain.FAQ, error) {
input.Question = strings.TrimSpace(input.Question)
input.Answer = strings.TrimSpace(input.Answer)
if input.Question == "" {
return domain.FAQ{}, fmt.Errorf("question is required")
}
if input.Answer == "" {
return domain.FAQ{}, fmt.Errorf("answer is required")
}
if input.Category != nil {
c := strings.TrimSpace(*input.Category)
if c == "" {
input.Category = nil
} else {
input.Category = &c
}
}
status, err := normalizeFAQStatus(input.Status)
if err != nil {
return domain.FAQ{}, err
}
input.Status = &status
return s.faqStore.CreateFAQ(ctx, input)
}
func (s *Service) UpdateFAQ(ctx context.Context, id int64, input domain.UpdateFAQInput) (domain.FAQ, error) {
if id <= 0 {
return domain.FAQ{}, fmt.Errorf("invalid faq id")
}
if input.Question != nil {
trimmed := strings.TrimSpace(*input.Question)
if trimmed == "" {
return domain.FAQ{}, fmt.Errorf("question cannot be empty")
}
input.Question = &trimmed
}
if input.Answer != nil {
trimmed := strings.TrimSpace(*input.Answer)
if trimmed == "" {
return domain.FAQ{}, fmt.Errorf("answer cannot be empty")
}
input.Answer = &trimmed
}
if input.Category != nil {
c := strings.TrimSpace(*input.Category)
input.Category = &c
}
if input.Status != nil {
status, err := normalizeFAQStatus(input.Status)
if err != nil {
return domain.FAQ{}, err
}
input.Status = &status
}
return s.faqStore.UpdateFAQ(ctx, id, input)
}
func (s *Service) GetFAQByID(ctx context.Context, id int64, includeInactive bool) (domain.FAQ, error) {
if id <= 0 {
return domain.FAQ{}, fmt.Errorf("invalid faq id")
}
return s.faqStore.GetFAQByID(ctx, id, includeInactive)
}
func (s *Service) ListFAQs(ctx context.Context, status *string, category *string, limit int32, offset int32) ([]domain.FAQ, int64, error) {
if status != nil {
normalized, err := normalizeFAQStatus(status)
if err != nil {
return nil, 0, err
}
status = &normalized
}
if category != nil {
trimmed := strings.TrimSpace(*category)
if trimmed == "" {
category = nil
} else {
category = &trimmed
}
}
if limit <= 0 {
limit = 20
}
if limit > 200 {
limit = 200
}
if offset < 0 {
offset = 0
}
return s.faqStore.ListFAQs(ctx, status, category, limit, offset)
}
func (s *Service) DeleteFAQ(ctx context.Context, id int64) error {
if id <= 0 {
return fmt.Errorf("invalid faq id")
}
return s.faqStore.DeleteFAQ(ctx, id)
}