szabadpalya/api/internal/store/campaigns.go

231 lines
5.8 KiB
Go

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
}