package store import ( "context" "errors" "github.com/jackc/pgx/v5" "github.com/szabadpalya/api/internal/store/dbgen" ) // The wire shapes below are what the frontend already expected from the // previous stack, so the view layer never learned about the schema and does // not have to now. type Creator struct { Name string `json:"name"` Initials string `json:"initials"` Location string `json:"location"` Blurb string `json:"blurb"` // Empty when the owner has no photo. The client falls back to Initials, // so this is omitted from the payload rather than sent as "". AvatarURL string `json:"avatarUrl,omitempty"` } type StorySection struct { Heading string `json:"heading"` Body []string `json:"body"` } type Tier struct { ID string `json:"id"` Type string `json:"type"` Price int `json:"price"` ListPrice *int `json:"listPrice,omitempty"` Title string `json:"title"` Includes []string `json:"includes"` Backers int `json:"backers"` InventoryLimit *int `json:"inventoryLimit,omitempty"` EstimatedDelivery string `json:"estimatedDelivery,omitempty"` Featured bool `json:"featured"` } type CampaignSummary struct { Slug string `json:"slug"` Title string `json:"title"` Category string `json:"category"` Summary string `json:"summary"` Art string `json:"art"` Creator Creator `json:"creator"` Goal int `json:"goal"` Raised int `json:"raised"` Backers int `json:"backers"` EndsAt string `json:"endsAt"` Status string `json:"status"` } type Campaign struct { CampaignSummary Story []StorySection `json:"story"` Tiers []Tier `json:"tiers"` Updates int `json:"updates"` Comments int `json:"comments"` } var ErrNotFound = errors.New("not found") // Only these statuses are visible without an admin session. A draft campaign // must 404 for the public exactly as a non-existent one does. var publicStatuses = []string{"AKTIV", "FINANSZIROZOTT", "LEZART"} // A pledge counts towards the total once the card is authorized; released and // refunded pledges drop back out. The raised amount and the backer count are // ALWAYS derived from these rows and stored nowhere. var countedPledges = []string{"AUTHORIZED", "CAPTURED"} var statusOut = map[string]string{ "AKTIV": "aktiv", "FINANSZIROZOTT": "finanszirozott", "LEZART": "lezart", } var tierTypeOut = map[string]string{ "DONATION": "donation", "DIGITAL": "digital", "PHYSICAL": "physical", } func outStatus(s string) string { if v, ok := statusOut[s]; ok { return v } return "aktiv" } func outTierType(s string) string { if v, ok := tierTypeOut[s]; ok { return v } return "donation" } func (s *Store) ListCampaigns(ctx context.Context) ([]CampaignSummary, error) { rows, err := s.q.ListCampaigns(ctx, dbgen.ListCampaignsParams{ Counted: countedPledges, Public: publicStatuses, }) if err != nil { return nil, err } out := make([]CampaignSummary, 0, len(rows)) for _, r := range rows { out = append(out, CampaignSummary{ Slug: r.Slug, Title: r.Title, Category: r.Category, Summary: r.Summary, Art: r.Art, Creator: Creator{ Name: r.Name, Initials: r.Initials, Location: r.Location, Blurb: r.Blurb, AvatarURL: r.AvatarUrl, }, Goal: r.GoalAmount, EndsAt: s.Day(r.EndsAt), Status: outStatus(r.Status), Raised: r.Raised, Backers: r.Backers, }) } return out, nil } func (s *Store) GetCampaignBySlug(ctx context.Context, slug string) (*Campaign, error) { r, err := s.q.GetCampaignBySlug(ctx, dbgen.GetCampaignBySlugParams{ Counted: countedPledges, Slug: slug, Public: publicStatuses, }) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, err } c := &Campaign{ CampaignSummary: CampaignSummary{ Slug: r.Slug, Title: r.Title, Category: r.Category, Summary: r.Summary, Art: r.Art, Creator: Creator{ Name: r.Name, Initials: r.Initials, Location: r.Location, Blurb: r.Blurb, AvatarURL: r.AvatarUrl, }, Goal: r.GoalAmount, EndsAt: s.Day(r.EndsAt), Status: outStatus(r.Status), Raised: r.Raised, Backers: r.Backers, }, Updates: r.Updates, Comments: r.Comments, } if c.Story, err = s.storySections(ctx, r.ID); err != nil { return nil, err } tiers, err := s.q.ListTiers(ctx, dbgen.ListTiersParams{ CampaignID: r.ID, Counted: countedPledges, }) if err != nil { return nil, err } c.Tiers = make([]Tier, 0, len(tiers)) for _, t := range tiers { tier := Tier{ ID: t.ID, Type: outTierType(t.Type), Price: t.Price, ListPrice: t.ListPrice, Title: t.Title, Includes: t.Includes, InventoryLimit: t.InventoryLimit, Featured: t.Featured, Backers: t.Backers, } if t.EstimatedDelivery != nil { tier.EstimatedDelivery = s.Day(*t.EstimatedDelivery) } c.Tiers = append(c.Tiers, tier) } return c, nil } func (s *Store) storySections(ctx context.Context, campaignID string) ([]StorySection, error) { rows, err := s.q.ListStorySections(ctx, campaignID) if err != nil { return nil, err } out := make([]StorySection, 0, len(rows)) for _, r := range rows { out = append(out, StorySection{Heading: r.Heading, Body: r.Body}) } return out, nil } type PlatformStats struct { ActiveCount int `json:"activeCount"` Backers int `json:"backers"` } func (s *Store) PlatformStats(ctx context.Context) (PlatformStats, error) { r, err := s.q.PlatformStats(ctx, countedPledges) if err != nil { return PlatformStats{}, err } return PlatformStats{ActiveCount: r.ActiveCount, Backers: r.Backers}, nil }