Implements PLAN.md Phase 8. internal/pushrules is the rule language: the spec's default ruleset, all six condition kinds, glob and word-boundary matching, intentional mentions switching off the legacy body rules, and rule ordering as Synapse has it. internal/pushserver keeps the rules, pushers and notifications and talks to push gateways. Push rules are the m.push_rules account data, written only by the push server; reads keep the user's own rules and the enabled/actions of each default and take the rest from the current defaults, so new default rules reach every user without a migration. PUT account_data/m.push_rules is refused with 405. The push server listens on the roomserver's emit stage and evaluates each event on its own worker for every local member and an invite's target. Notifications and per-room unread counts are stored in migration 0010; counts are a seventh sync token stream, so /sync reports unread_notifications and picks up count changes made after the event or by a receipt from another device. m.read and m.read.private receipts clear the counts; leaving a room clears it. Pushers (http kind only) push through a bounded sender pool with retries, honour event_id_only, drop push keys the gateway rejects, and go away with their device. GET /notifications pages the history. New config section push: enabled, include_content, gateway_timeout, notification_retention. Account data writes now wake the user's blocked syncs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
4.7 KiB
Go
155 lines
4.7 KiB
Go
// Package storage defines the interfaces every Warp storage driver
|
|
// implements, plus the registry that turns a config into a Database.
|
|
//
|
|
// Drivers live in sub-packages (sqlite, postgres, pocketbase) and register
|
|
// themselves from an init function, the database/sql way. Nothing in this
|
|
// package imports a driver, so components can depend on the interfaces
|
|
// without pulling in every database engine.
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"maps"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Driver names. These are the values accepted by database.driver in the
|
|
// config file.
|
|
const (
|
|
DriverSQLite = "sqlite"
|
|
DriverPostgres = "postgres"
|
|
DriverPocketBase = "pocketbase"
|
|
)
|
|
|
|
// ErrNotImplemented is returned by a driver that is compiled in but not
|
|
// finished — currently PocketBase.
|
|
var ErrNotImplemented = errors.New("not implemented by this storage driver")
|
|
|
|
// ErrNoSuchSetting is returned by SettingsStore.Get for an unknown key.
|
|
var ErrNoSuchSetting = errors.New("no such setting")
|
|
|
|
// Config is the subset of the file configuration a driver needs.
|
|
type Config struct {
|
|
Driver string
|
|
ConnectionString string
|
|
MaxOpenConns int
|
|
MaxIdleConns int
|
|
ConnMaxLifetime time.Duration
|
|
}
|
|
|
|
// Database is the handle every component gets. Component-specific stores
|
|
// (userapi, roomserver, syncapi...) hang off it as they are implemented.
|
|
type Database interface {
|
|
// Driver reports which driver this is, for logs and admin output.
|
|
Driver() string
|
|
|
|
// Ping checks the connection is alive; used by /healthz.
|
|
Ping(ctx context.Context) error
|
|
|
|
// Migrate applies pending migrations.
|
|
Migrate(ctx context.Context) error
|
|
|
|
// PendingMigrations lists migrations that Migrate would apply.
|
|
PendingMigrations(ctx context.Context) ([]string, error)
|
|
|
|
// Settings is the server_settings key/value store backing the runtime
|
|
// settings service (federation and registration switches).
|
|
Settings() SettingsStore
|
|
|
|
// Users is the userapi store: accounts, devices, tokens, account data.
|
|
Users() UserStore
|
|
|
|
// Rooms is the roomserver store: rooms, events, room state, aliases.
|
|
Rooms() RoomStore
|
|
|
|
// Sync is the syncapi store: receipts, to-device messages, presence and
|
|
// uploaded filters.
|
|
Sync() SyncStore
|
|
|
|
// Federation is the federation store: other servers' keys, the
|
|
// outbound queue and its backoff, and the inbound transaction log.
|
|
Federation() FederationStore
|
|
|
|
// Media is the mediaapi store: the metadata of uploaded and cached
|
|
// remote media, and of their thumbnails.
|
|
Media() MediaStore
|
|
|
|
// Keys is the keyserver store: device keys, one-time and fallback
|
|
// keys, cross-signing keys and their signatures, the device list
|
|
// stream and the server-side key backup.
|
|
Keys() KeyStore
|
|
|
|
// Push is the push store: pushers, and the notifications push rules
|
|
// produce with their per-room unread counts.
|
|
Push() PushStore
|
|
|
|
// Close releases the connection pool.
|
|
Close() error
|
|
}
|
|
|
|
// SettingsStore persists the handful of settings that are mutable at
|
|
// runtime through the admin API.
|
|
type SettingsStore interface {
|
|
// Get returns the raw value for key. The boolean reports presence.
|
|
Get(ctx context.Context, key string) (string, bool, error)
|
|
|
|
// GetAll returns every setting.
|
|
GetAll(ctx context.Context) (map[string]string, error)
|
|
|
|
// Set writes key, overwriting any previous value.
|
|
Set(ctx context.Context, key, value string) error
|
|
|
|
// Delete removes key, reverting it to its configured default. Deleting
|
|
// a key that is not there is not an error.
|
|
Delete(ctx context.Context, key string) error
|
|
}
|
|
|
|
// OpenFunc opens a database for a driver.
|
|
type OpenFunc func(ctx context.Context, cfg Config, log *slog.Logger) (Database, error)
|
|
|
|
var (
|
|
registryMu sync.RWMutex
|
|
registry = map[string]OpenFunc{}
|
|
)
|
|
|
|
// Register makes a driver available to Open. It panics on a duplicate
|
|
// registration, which can only be a programming error.
|
|
func Register(name string, open OpenFunc) {
|
|
registryMu.Lock()
|
|
defer registryMu.Unlock()
|
|
if _, dup := registry[name]; dup {
|
|
panic("storage: driver " + name + " registered twice")
|
|
}
|
|
registry[name] = open
|
|
}
|
|
|
|
// Drivers lists the registered driver names, sorted.
|
|
func Drivers() []string {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
return slices.Sorted(maps.Keys(registry))
|
|
}
|
|
|
|
// Open connects using the driver named in cfg.
|
|
func Open(ctx context.Context, cfg Config, log *slog.Logger) (Database, error) {
|
|
registryMu.RLock()
|
|
open, ok := registry[cfg.Driver]
|
|
registryMu.RUnlock()
|
|
if !ok {
|
|
return nil, fmt.Errorf("storage: unknown driver %q (compiled in: %v)", cfg.Driver, Drivers())
|
|
}
|
|
if log == nil {
|
|
log = slog.Default()
|
|
}
|
|
db, err := open(ctx, cfg, log.With("driver", cfg.Driver))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("storage: open %s: %w", cfg.Driver, err)
|
|
}
|
|
return db, nil
|
|
}
|