Stage 10: admin console & dictionary ops (complaint review, hot-reload, broadcasts) (#11)
This commit was merged in pull request #11.
This commit is contained in:
@@ -420,6 +420,68 @@ func (svc *Service) FileComplaint(ctx context.Context, gameID, accountID uuid.UU
|
||||
})
|
||||
}
|
||||
|
||||
// ListComplaints returns word-check complaints for the admin review queue,
|
||||
// newest first. status filters by lifecycle state ("" = all); limit is clamped
|
||||
// to a sane page size and offset is floored at zero.
|
||||
func (svc *Service) ListComplaints(ctx context.Context, status string, limit, offset int) ([]Complaint, error) {
|
||||
return svc.store.ListComplaints(ctx, status, clampPageSize(limit), max(0, offset))
|
||||
}
|
||||
|
||||
// GetComplaint loads a single complaint for the admin detail view.
|
||||
func (svc *Service) GetComplaint(ctx context.Context, id uuid.UUID) (Complaint, error) {
|
||||
return svc.store.GetComplaint(ctx, id)
|
||||
}
|
||||
|
||||
// CountComplaints returns the number of complaints, optionally restricted to a
|
||||
// status, for the admin queue pager and the dashboard counts.
|
||||
func (svc *Service) CountComplaints(ctx context.Context, status string) (int, error) {
|
||||
return svc.store.CountComplaints(ctx, status)
|
||||
}
|
||||
|
||||
// ResolveComplaint closes a complaint with an operator disposition (reject /
|
||||
// accept_add / accept_remove) and an optional note. An accepted complaint then
|
||||
// appears in DictionaryChanges until a rebuilt dictionary is loaded and the
|
||||
// change is marked applied. It returns ErrInvalidConfig for an unknown
|
||||
// disposition and ErrNotFound when no complaint matches.
|
||||
func (svc *Service) ResolveComplaint(ctx context.Context, id uuid.UUID, disposition, note string) (Complaint, error) {
|
||||
if !validDisposition(disposition) {
|
||||
return Complaint{}, fmt.Errorf("%w: complaint disposition %q", ErrInvalidConfig, disposition)
|
||||
}
|
||||
return svc.store.ResolveComplaint(ctx, id, disposition, note, svc.clock())
|
||||
}
|
||||
|
||||
// DictionaryChanges returns the pending wordlist edits implied by resolved,
|
||||
// accepted complaints not yet marked applied — the input to the offline DAWG
|
||||
// rebuild.
|
||||
func (svc *Service) DictionaryChanges(ctx context.Context) ([]DictionaryChange, error) {
|
||||
rows, err := svc.store.ListDictionaryChanges(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]DictionaryChange, 0, len(rows))
|
||||
for _, c := range rows {
|
||||
ch := DictionaryChange{
|
||||
ComplaintID: c.ID,
|
||||
Variant: c.Variant,
|
||||
Word: c.Word,
|
||||
Add: c.Disposition == DispositionAcceptAdd,
|
||||
Note: c.Note,
|
||||
}
|
||||
if c.ResolvedAt != nil {
|
||||
ch.ResolvedAt = *c.ResolvedAt
|
||||
}
|
||||
out = append(out, ch)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MarkChangesApplied records that every pending accepted change for variant has
|
||||
// been folded into the dictionary version that was just hot-reloaded, removing
|
||||
// them from DictionaryChanges. It returns the number of changes marked.
|
||||
func (svc *Service) MarkChangesApplied(ctx context.Context, variant engine.Variant, version string) (int64, error) {
|
||||
return svc.store.MarkChangesApplied(ctx, variant.String(), version)
|
||||
}
|
||||
|
||||
// Hint reveals the top-scoring legal play for the requesting player on their
|
||||
// turn, spending one hint from their per-game allowance and then their profile
|
||||
// wallet. It returns ErrHintsDisabled, ErrNoHintsLeft or ErrNoHintAvailable as
|
||||
@@ -583,6 +645,23 @@ func (svc *Service) ListForAccount(ctx context.Context, accountID uuid.UUID) ([]
|
||||
return svc.store.ListGamesForAccount(ctx, accountID)
|
||||
}
|
||||
|
||||
// GameByID returns a game with its seats for the admin console detail view.
|
||||
func (svc *Service) GameByID(ctx context.Context, id uuid.UUID) (Game, error) {
|
||||
return svc.store.GetGame(ctx, id)
|
||||
}
|
||||
|
||||
// ListGames returns games for the admin list, newest-updated first, paginated,
|
||||
// optionally filtered by status.
|
||||
func (svc *Service) ListGames(ctx context.Context, status string, limit, offset int) ([]Game, error) {
|
||||
return svc.store.ListGames(ctx, status, clampPageSize(limit), max(0, offset))
|
||||
}
|
||||
|
||||
// CountGames returns the game count, optionally filtered by status, for the admin
|
||||
// list pager and dashboard.
|
||||
func (svc *Service) CountGames(ctx context.Context, status string) (int, error) {
|
||||
return svc.store.CountGames(ctx, status)
|
||||
}
|
||||
|
||||
// History returns a game's full, dictionary-independent move journal.
|
||||
func (svc *Service) History(ctx context.Context, gameID uuid.UUID) (HistoryView, error) {
|
||||
g, err := svc.store.GetGame(ctx, gameID)
|
||||
@@ -770,6 +849,29 @@ func normalizeWord(word string) string {
|
||||
return strings.ToLower(strings.TrimSpace(word))
|
||||
}
|
||||
|
||||
// validDisposition reports whether d is an accepted complaint disposition.
|
||||
func validDisposition(d string) bool {
|
||||
switch d {
|
||||
case DispositionReject, DispositionAcceptAdd, DispositionAcceptRemove:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// clampPageSize bounds an admin list page size to [1, 200], defaulting an unset
|
||||
// (non-positive) request to 50.
|
||||
func clampPageSize(limit int) int {
|
||||
switch {
|
||||
case limit <= 0:
|
||||
return 50
|
||||
case limit > 200:
|
||||
return 200
|
||||
default:
|
||||
return limit
|
||||
}
|
||||
}
|
||||
|
||||
// randomSeed returns an unpredictable bag seed, falling back to the clock if the
|
||||
// system source fails.
|
||||
func randomSeed() int64 {
|
||||
|
||||
Reference in New Issue
Block a user