Add backend foundation and config-driven workbench
This commit is contained in:
159
backend/internal/service/home_service.go
Normal file
159
backend/internal/service/home_service.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/store/postgres"
|
||||
)
|
||||
|
||||
type HomeService struct {
|
||||
store *postgres.Store
|
||||
}
|
||||
|
||||
type ListCardsInput struct {
|
||||
ChannelCode string
|
||||
ChannelType string
|
||||
PlatformAppID string
|
||||
TenantCode string
|
||||
Slot string
|
||||
Limit int
|
||||
}
|
||||
|
||||
type CardResult struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Subtitle *string `json:"subtitle,omitempty"`
|
||||
CoverURL *string `json:"coverUrl,omitempty"`
|
||||
DisplaySlot string `json:"displaySlot"`
|
||||
DisplayPriority int `json:"displayPriority"`
|
||||
Event *struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Summary *string `json:"summary,omitempty"`
|
||||
} `json:"event,omitempty"`
|
||||
HTMLURL *string `json:"htmlUrl,omitempty"`
|
||||
}
|
||||
|
||||
type HomeResult struct {
|
||||
Tenant struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
} `json:"tenant"`
|
||||
Channel struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
PlatformAppID *string `json:"platformAppId,omitempty"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
} `json:"channel"`
|
||||
Cards []CardResult `json:"cards"`
|
||||
}
|
||||
|
||||
func NewHomeService(store *postgres.Store) *HomeService {
|
||||
return &HomeService{store: store}
|
||||
}
|
||||
|
||||
func (s *HomeService) ListCards(ctx context.Context, input ListCardsInput) ([]CardResult, error) {
|
||||
entry, err := s.resolveEntry(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := s.store.ListCardsForEntry(ctx, entry.TenantID, &entry.ID, normalizeSlot(input.Slot), time.Now().UTC(), input.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapCards(cards), nil
|
||||
}
|
||||
|
||||
func (s *HomeService) GetHome(ctx context.Context, input ListCardsInput) (*HomeResult, error) {
|
||||
entry, err := s.resolveEntry(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := s.store.ListCardsForEntry(ctx, entry.TenantID, &entry.ID, normalizeSlot(input.Slot), time.Now().UTC(), input.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &HomeResult{
|
||||
Cards: mapCards(cards),
|
||||
}
|
||||
result.Tenant.ID = entry.TenantID
|
||||
result.Tenant.Code = entry.TenantCode
|
||||
result.Tenant.Name = entry.TenantName
|
||||
result.Channel.ID = entry.ID
|
||||
result.Channel.Code = entry.ChannelCode
|
||||
result.Channel.Type = entry.ChannelType
|
||||
result.Channel.PlatformAppID = entry.PlatformAppID
|
||||
result.Channel.DisplayName = entry.DisplayName
|
||||
result.Channel.Status = entry.Status
|
||||
result.Channel.IsDefault = entry.IsDefault
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HomeService) resolveEntry(ctx context.Context, input ListCardsInput) (*postgres.EntryChannel, error) {
|
||||
entry, err := s.store.FindEntryChannel(ctx, postgres.FindEntryChannelParams{
|
||||
ChannelCode: strings.TrimSpace(input.ChannelCode),
|
||||
ChannelType: strings.TrimSpace(input.ChannelType),
|
||||
PlatformAppID: strings.TrimSpace(input.PlatformAppID),
|
||||
TenantCode: strings.TrimSpace(input.TenantCode),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "entry_channel_not_found", "entry channel not found")
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func normalizeSlot(slot string) string {
|
||||
slot = strings.TrimSpace(slot)
|
||||
if slot == "" {
|
||||
return "home_primary"
|
||||
}
|
||||
return slot
|
||||
}
|
||||
|
||||
func mapCards(cards []postgres.Card) []CardResult {
|
||||
results := make([]CardResult, 0, len(cards))
|
||||
for _, card := range cards {
|
||||
item := CardResult{
|
||||
ID: card.PublicID,
|
||||
Type: card.CardType,
|
||||
Title: card.Title,
|
||||
Subtitle: card.Subtitle,
|
||||
CoverURL: card.CoverURL,
|
||||
DisplaySlot: card.DisplaySlot,
|
||||
DisplayPriority: card.DisplayPriority,
|
||||
HTMLURL: card.HTMLURL,
|
||||
}
|
||||
if card.EventPublicID != nil || card.EventDisplayName != nil {
|
||||
item.Event = &struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Summary *string `json:"summary,omitempty"`
|
||||
}{
|
||||
Summary: card.EventSummary,
|
||||
}
|
||||
if card.EventPublicID != nil {
|
||||
item.Event.ID = *card.EventPublicID
|
||||
}
|
||||
if card.EventDisplayName != nil {
|
||||
item.Event.DisplayName = *card.EventDisplayName
|
||||
}
|
||||
}
|
||||
results = append(results, item)
|
||||
}
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user