owpengram-server/internal/store/account_rating.go
Egor Egorov fff8de783a
feat: add NFT usernames and bot verification (#22)
Implements collectible usernames, official verification workflows, and third-party bot verification after maintainer protocol and migration review.

The composite activity/moderation rating remains an admin-only read model; Telegram Stars Rating wire fields stay unset pending a dedicated official-semantics implementation.

Reviewed-Head: 2796345775ea0f908fb7734601e5e1dee4b653b9
Original-Head: fa082b892fd5180c9c9bc53c81c21cf5d250a75b

Co-authored-by: Egor Egorov <business.egor.sg@gmail.com>
2026-07-28 01:18:00 +08:00

49 lines
2.8 KiB
Go

package store
import (
"context"
"telesrv/internal/domain"
)
// AccountRatingStore owns the composite rating read model and its contribution
// ledger.
//
// The read model is derived: SaveAccountRating writes a recomputed projection,
// AccountRatingSignals gathers the raw inputs from the contributing tables, and
// the ledger keeps manual adjustments that must survive a recompute.
type AccountRatingStore interface {
// AccountRating returns the stored projection. Missing rows return
// domain.ErrAccountRatingNotFound so callers can distinguish "never computed"
// from "computed as zero".
AccountRating(ctx context.Context, userID int64) (domain.AccountRating, error)
// AccountRatingBatch resolves several users in one round trip. Users without
// a row are absent from the map.
AccountRatingBatch(ctx context.Context, userIDs []int64) (map[int64]domain.AccountRating, error)
// SaveAccountRating upserts the projection using optimistic concurrency on
// the stored version; a stale version reports changed=false.
SaveAccountRating(ctx context.Context, rating domain.AccountRating) (stored domain.AccountRating, changed bool, err error)
// AccountRatingSignals gathers the raw contribution snapshot for one user,
// including the manual total carried from the ledger.
AccountRatingSignals(ctx context.Context, userID int64) (domain.AccountRatingSignals, error)
// AdjustAccountRating appends a manual adjustment. Replaying the same
// CommandKey returns the recorded event and applied=false.
AdjustAccountRating(ctx context.Context, req domain.AdjustAccountRatingRequest) (event domain.AccountRatingEvent, applied bool, err error)
// ListAccountRatings is the admin leaderboard query with keyset paging.
ListAccountRatings(ctx context.Context, filter domain.AccountRatingFilter) ([]domain.AccountRating, error)
// AccountRatingEvents returns the ledger for one user, newest first.
AccountRatingEvents(ctx context.Context, userID int64, limit int) ([]domain.AccountRatingEvent, error)
// StaleAccountRatings returns user ids whose projection is older than the
// given horizon, for the background recompute worker.
StaleAccountRatings(ctx context.Context, olderThanUnix int64, limit int) ([]int64, error)
// UnratedAccounts returns user ids that have no projection at all, oldest
// account first, for the same worker.
//
// Without this the read model can never populate itself: StaleAccountRatings
// walks account_rating, so it can only refresh rows that already exist, and the
// very first row for a user would have to come from an operator recomputing
// that user by hand. Seeding is what makes the local admin leaderboard useful.
//
// Deleted accounts and bots are excluded: neither has a rating to show.
UnratedAccounts(ctx context.Context, limit int) ([]int64, error)
}