Documentation
¶
Overview ¶
Package store is the persistence layer (PRD §4.2): SQLite behind a Store interface so a move to Postgres is a driver swap, not a rewrite.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDomainTaken = errors.New("domain already mapped to another account")
ErrDomainTaken means a domain is already mapped to a different member.
var ErrKeyMismatch = errors.New("username registered with a different key")
ErrKeyMismatch means a username is already registered with another key.
var ErrQuotaExceeded = errors.New("qrypt invite quota exceeded")
ErrQuotaExceeded means a member has hit their qrypt.chat invite quota.
Functions ¶
This section is empty.
Types ¶
type AdminAction ¶
AdminAction is one entry in the admin audit log.
type ChatMessage ¶
ChatMessage is one line of an agent@ conversation.
type DomainMap ¶
DomainMap binds a custom domain to a member's homepage (the public_html that is also served at /~name). Used to serve, e.g., https://chovy.com from users/chovy/public_html.
type FilesAccess ¶ added in v0.1.1
FilesAccess is a user's SFTP access record. QuotaBytes is a per-user override (0 means "use the server default"); Revoked blocks SFTP without affecting the BBS login.
type MatchRow ¶
type MatchRow struct {
ID int64
Game string
P0 string
P1 string
Winner int
Reason string
Moves []games.Move
RatingAfter [2]float64
StartedAt time.Time
EndedAt time.Time
}
MatchRow is a recorded match, including its moves for replay.
type NewsArticle ¶
type NewsArticle struct {
Group string
Num int64
MsgID string
Subject string
From string // the From: header (stamped to the posting member)
Refs string // the References: header
Date string // the Date: header as posted (RFC1123Z)
Body string
Lines int
Bytes int
CreatedAt time.Time
}
NewsArticle is one stored article within a group. Headers beyond these are reconstructed at serve time (Message-ID, Newsgroups, Path) from these fields.
type NewsGroup ¶
type NewsGroup struct {
Name string
Description string
Posting bool
Count int64
Low int64
High int64
CreatedAt time.Time
}
NewsGroup is a newsgroup plus the cached article-number bounds NNTP clients expect (Low/High/Count). Empty groups report Low=1, High=0, Count=0.
type SessionRow ¶
type SessionRow struct {
ID int64
Username string
Remote string
Route string
Started time.Time
Ended time.Time
EndedValid bool // false while the session is still open
}
SessionRow is one connection record from the audit trail.
type Store ¶
type Store interface {
// EnsureUser returns the user with this name, creating it with the given
// kind and key fingerprint on first sight. If the name exists with a
// different fingerprint, ErrKeyMismatch is returned.
EnsureUser(name, kind, pubkeyFP string) (User, error)
// UserByFingerprint finds an account by SSH key fingerprint.
UserByFingerprint(fp string) (User, bool, error)
// UserByName finds an account by exact username (no creation).
UserByName(name string) (User, bool, error)
// LastSeen reports the start of the user's most recent session.
LastSeen(userID int64) (time.Time, bool, error)
// SetEmailVerification records the account's email and a fresh
// confirmation token (a link token or a short code), marking it unverified
// until the token is consumed.
SetEmailVerification(userID int64, email, token string) error
// VerifyEmail consumes a confirmation token: on match it marks the
// account verified, clears the token, and returns the account.
VerifyEmail(token string) (User, bool, error)
// ConfirmEmailCode is the interactive (join@) counterpart to VerifyEmail:
// it matches the code against the one stored for THIS user (codes are
// short and not globally unique), and on match marks the account verified
// and clears the code. Returns ok=false on a wrong/empty code.
ConfirmEmailCode(userID int64, code string) (User, bool, error)
// SetPremiumPayment records the CoinPay payment id of a pending premium
// charge so a later visit can verify whether it settled.
SetPremiumPayment(userID int64, payID string) error
// GrantPremium marks the account as a lifetime premium member (the $99
// Founding Lifetime membership), recording the CoinPay payment reference. Idempotent.
GrantPremium(userID int64, paymentRef string) error
RecordSession(userID int64, username, remote, route string) (int64, error)
EndSession(sessionID int64) error
AddScore(userID int64, game string, score int64) error
TopScores(game string, n int) ([]Score, error)
// Pod subscription (paid membership, e.g. $1/mo via CoinPay).
PodPaidUntil(userID int64) (time.Time, bool, error)
GrantPod(userID int64, until time.Time, paymentRef string) error
// Chat transcripts for the agent@ surface.
AddChat(userID int64, username, role, text string) error
RecentChats(username string, n int) ([]ChatMessage, error)
// SendMessage leaves a note from→to in the recipient's inbox.
SendMessage(from, to, body string) error
// SendMessageMulti delivers one body to every recipient in to, in a single
// transaction (used for group + broadcast messages so a partial failure
// doesn't leave some inboxes written and others not). Duplicate and empty
// recipients are skipped; an empty list is a no-op. Returns the number of
// inboxes written.
SendMessageMulti(from string, to []string, body string) (int, error)
// Inbox returns up to n messages addressed to username, newest first.
Inbox(username string, n int) ([]Message, error)
// UnreadCount reports how many unread messages username has waiting.
UnreadCount(username string) (int, error)
// MarkRead marks the given message ids read (scoped to username so a member
// can only clear their own mail). Empty ids is a no-op.
MarkRead(username string, ids []int64) error
// OnlineUsers reports the set of usernames with an open session (no
// ended_at), for the members directory presence dots.
OnlineUsers() (map[string]bool, error)
// Custom domains mapped to a member's homepage (public_html).
// MapDomain binds domain→username, returning whether a new row was created.
// It returns ErrDomainTaken if the domain is already claimed by someone else;
// re-binding to the same owner is a no-op and returns false.
MapDomain(domain, username string) (bool, error)
UnmapDomain(domain, username string) error
DomainUser(domain string) (string, bool, error)
DomainsForUser(username string) ([]string, error)
AllDomains() ([]DomainMap, error)
// ListUsers returns up to limit accounts, most recently created first.
ListUsers(limit int) ([]User, error)
// SetBanned suspends (or restores) an account; banned accounts are blocked
// at login by the SSH routes.
SetBanned(userID int64, banned bool) error
// RecentSessions returns the last n session rows (the audit trail).
RecentSessions(n int) ([]SessionRow, error)
// LogAdminAction records one privileged action for the audit log.
LogAdminAction(admin, action, target, detail string) error
// RecentAdminActions returns the last n logged admin actions.
RecentAdminActions(n int) ([]AdminAction, error)
// RecentChatsAll returns the last n agent@ messages across all users, for
// moderation review.
RecentChatsAll(n int) ([]ChatRow, error)
// DisabledPlugins reports the set of plugin IDs currently switched off.
DisabledPlugins() (map[string]bool, error)
// SetPluginDisabled enables or disables a plugin by ID. Idempotent.
SetPluginDisabled(id string, disabled bool) error
// Rating returns a player's current rating for a game, or
// games.DefaultRating if they have no rated history there. It satisfies
// games.Store so the matchmaker can read ratings.
Rating(user, game string) (float64, error)
// SaveMatch records a finished match and upserts both players' ratings.
// It satisfies games.Store.
SaveMatch(games.FinishedMatch) error
// TopRatings returns the n highest-rated players for a game.
TopRatings(game string, n int) ([]RatingRow, error)
// RecentMatches returns the last n matches for a game, newest first.
RecentMatches(game string, n int) ([]MatchRow, error)
// MatchByID returns one match (with its moves, for replay).
MatchByID(id int64) (MatchRow, bool, error)
// MintAPIToken creates and stores a fresh bearer token for the WebSocket
// game endpoint, bound to username. Returns the token.
MintAPIToken(username string) (string, error)
// UserByToken resolves an API token to its account name.
UserByToken(token string) (string, bool, error)
// QryptInviteCount reports how many qrypt.chat invites username has issued.
QryptInviteCount(username string) (int, error)
// RecordQryptInvite records one issued invite (its jti, for audit and as
// the per-member quota counter) against username. It returns
// ErrQuotaExceeded if the member is already at or above quota.
RecordQryptInvite(username, jti string, quota int) error
// EnsureNewsGroup creates a newsgroup if absent (idempotent), setting the
// description only on first creation.
EnsureNewsGroup(name, description string) error
// NewsGroups lists every group with its article counts, name-sorted.
NewsGroups() ([]NewsGroup, error)
// NewsGroup returns one group (with counts), or ok=false if unknown.
NewsGroup(name string) (NewsGroup, bool, error)
// NewsArticleByNum fetches an article by its per-group sequence number.
NewsArticleByNum(group string, num int64) (NewsArticle, bool, error)
// NewsArticleByMsgID fetches the first article with this Message-ID (any
// group it was posted to).
NewsArticleByMsgID(msgID string) (NewsArticle, bool, error)
// NewsArticlesRange returns articles in [from,to] (inclusive) for a group,
// ordered by number, for OVER/XOVER.
NewsArticlesRange(group string, from, to int64) ([]NewsArticle, error)
// InsertNewsArticle stores an article in a group, assigning the next
// per-group number, and returns the stored row (with its number).
InsertNewsArticle(a NewsArticle) (NewsArticle, error)
// FilesAccess returns a user's SFTP access record (per-user quota override
// and revoked flag). A user with no row reports the zero value
// (QuotaBytes 0 = use the server default, Revoked false).
FilesAccess(userID int64) (FilesAccess, error)
// SetFilesQuota sets a per-user quota override in bytes (0 clears the
// override, falling back to the server default). Idempotent upsert.
SetFilesQuota(userID, bytes int64) error
// SetFilesRevoked revokes (or restores) a user's SFTP access without
// touching their BBS login. Idempotent upsert.
SetFilesRevoked(userID int64, revoked bool) error
// FilesSetting reads a Files service setting (e.g. the public-write mode).
FilesSetting(key string) (string, bool, error)
// SetFilesSetting writes a Files service setting. Idempotent upsert.
SetFilesSetting(key, value string) error
Close() error
}
Store is the persistence contract shared by all plugins.
type User ¶
type User struct {
ID int64
Name string
Kind string
PubKeyFP string
Email string
EmailVerified bool
Premium bool // paid the one-time lifetime membership
PremiumPayID string // CoinPay payment id of the pending/settled premium charge
Banned bool // suspended by an admin (blocked at login)
CreatedAt time.Time
}
User is a persisted account (member or agent; guests are never stored).