Documentation
¶
Index ¶
- Constants
- Variables
- func EncodeCursor(data CursorData) string
- func NewID() string
- func NormalizeLimit(limit int) int
- type APIKey
- type AdminProject
- type AdminProjectMember
- type AdminUser
- type AdminUserDetail
- type AuthEvent
- type AuthRequest
- type CursorData
- type Membership
- type Migration
- type PaginatedResult
- type Project
- type RateLimitEvent
- type ServerDB
- func (db *ServerDB) AddMember(projectID, userID, role, invitedByUserID string) (*Membership, error)
- func (db *ServerDB) AdminGetProject(id string) (*AdminProject, error)
- func (db *ServerDB) AdminGetUser(id string) (*AdminUserDetail, error)
- func (db *ServerDB) AdminListProjectMembers(projectID string) ([]AdminProjectMember, error)
- func (db *ServerDB) AdminListProjects(query string, includeDeleted bool, limit int, cursor string) (*PaginatedResult[AdminProject], error)
- func (db *ServerDB) AdminListUsers(query string, limit int, cursor string) (*PaginatedResult[AdminUser], error)
- func (db *ServerDB) Authorize(projectID, userID, requiredRole string) error
- func (db *ServerDB) CanDeleteProject(projectID, userID string) error
- func (db *ServerDB) CanManageMembers(projectID, userID string) error
- func (db *ServerDB) CanPullEvents(projectID, userID string) error
- func (db *ServerDB) CanPushEvents(projectID, userID string) error
- func (db *ServerDB) CanViewProject(projectID, userID string) error
- func (db *ServerDB) CleanupAuthEvents(olderThan time.Duration) (int64, error)
- func (db *ServerDB) CleanupExpiredAuthRequests() (int64, error)
- func (db *ServerDB) CleanupRateLimitEvents(olderThan time.Duration) (int64, error)
- func (db *ServerDB) Close() error
- func (db *ServerDB) CompleteAuthRequest(deviceCode string) (*AuthRequest, error)
- func (db *ServerDB) CountAdmins() (int, error)
- func (db *ServerDB) CountMembers() (int, error)
- func (db *ServerDB) CountProjects() (int, error)
- func (db *ServerDB) CountUsers() (int, error)
- func (db *ServerDB) CreateAuthRequest(email string) (*AuthRequest, error)
- func (db *ServerDB) CreateProject(name, description, ownerUserID string) (*Project, error)
- func (db *ServerDB) CreateProjectWithID(id, name, description, ownerUserID string) (*Project, error)
- func (db *ServerDB) CreateUser(email string) (*User, error)
- func (db *ServerDB) ForceExpireAuthRequestForTest(id string, expiresAt time.Time)
- func (db *ServerDB) GenerateAPIKey(userID, name, scopes string, expiresAt *time.Time) (string, *APIKey, error)
- func (db *ServerDB) GetAuthRequestByDeviceCode(deviceCode string) (*AuthRequest, error)
- func (db *ServerDB) GetAuthRequestByUserCode(userCode string) (*AuthRequest, error)
- func (db *ServerDB) GetMembership(projectID, userID string) (*Membership, error)
- func (db *ServerDB) GetPendingExpiredAuthRequests() ([]AuthRequest, error)
- func (db *ServerDB) GetProject(id string, includeSoftDeleted bool) (*Project, error)
- func (db *ServerDB) GetProjectEventCount(projectID string) (int, *time.Time, error)
- func (db *ServerDB) GetSyncCursor(projectID, clientID string) (*SyncCursor, error)
- func (db *ServerDB) GetUserByEmail(email string) (*User, error)
- func (db *ServerDB) GetUserByID(id string) (*User, error)
- func (db *ServerDB) InsertAuthEvent(authRequestID, email, eventType, metadata string) error
- func (db *ServerDB) InsertRateLimitEvent(keyID, ip, endpointClass string) error
- func (db *ServerDB) IsUserAdmin(userID string) (bool, error)
- func (db *ServerDB) ListAPIKeys(userID string) ([]*APIKey, error)
- func (db *ServerDB) ListAPIKeysForUser(userID string) ([]*APIKey, error)
- func (db *ServerDB) ListMembers(projectID string) ([]*Membership, error)
- func (db *ServerDB) ListProjectsForUser(userID string) ([]*Project, error)
- func (db *ServerDB) ListSyncCursorsForProject(projectID string) ([]SyncCursor, error)
- func (db *ServerDB) ListUsers() ([]*User, error)
- func (db *ServerDB) Ping() error
- func (db *ServerDB) QueryAuthEvents(eventType, email, from, to string, limit int, cursor string) (*PaginatedResult[AuthEvent], error)
- func (db *ServerDB) QueryRateLimitEvents(keyID, ip, from, to string, limit int, cursor string) (*PaginatedResult[RateLimitEvent], error)
- func (db *ServerDB) RemoveMember(projectID, userID string) error
- func (db *ServerDB) RevokeAPIKey(keyID, userID string) error
- func (db *ServerDB) RunMigrations() (int, error)
- func (db *ServerDB) SetAuthRequestAPIKey(id, apiKeyID string) error
- func (db *ServerDB) SetEmailVerified(userID string) error
- func (db *ServerDB) SetUserAdmin(email string, isAdmin bool) error
- func (db *ServerDB) SoftDeleteProject(id string) error
- func (db *ServerDB) UpdateMemberRole(projectID, userID, newRole string) error
- func (db *ServerDB) UpdateProject(id, name, description string) (*Project, error)
- func (db *ServerDB) UpdateProjectEventCount(projectID string, newEvents int, lastEventAt time.Time) error
- func (db *ServerDB) UpsertSyncCursor(projectID, clientID string, lastEventID int64) error
- func (db *ServerDB) VerifyAPIKey(plaintextKey string) (*APIKey, *User, error)
- func (db *ServerDB) VerifyAuthRequest(userCode, userID string) error
- type SyncCursor
- type User
- type UserProject
Constants ¶
const ( RoleOwner = "owner" RoleWriter = "writer" RoleReader = "reader" )
Role constants
const ( AuthEventStarted = "started" AuthEventCodeVerified = "code_verified" AuthEventKeyIssued = "key_issued" AuthEventExpired = "expired" AuthEventFailed = "failed" )
Auth event type constants.
const ( AuthStatusPending = "pending" AuthStatusVerified = "verified" AuthStatusExpired = "expired" AuthStatusUsed = "used" AuthRequestTTL = 15 * time.Minute PollInterval = 5 )
const ( DefaultPageLimit = 50 MaxPageLimit = 200 )
const ServerSchemaVersion = 3
ServerSchemaVersion is the current server database schema version
Variables ¶
var Migrations = []Migration{
{
Version: 2,
Description: "Add auth_requests table for device auth flow",
SQL: `CREATE TABLE IF NOT EXISTS auth_requests (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
device_code TEXT UNIQUE NOT NULL,
user_code TEXT UNIQUE NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
user_id TEXT,
api_key_id TEXT,
expires_at DATETIME NOT NULL,
verified_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_auth_requests_device_code ON auth_requests(device_code);
CREATE INDEX IF NOT EXISTS idx_auth_requests_user_code ON auth_requests(user_code);
CREATE INDEX IF NOT EXISTS idx_auth_requests_status ON auth_requests(status);
CREATE INDEX IF NOT EXISTS idx_auth_requests_cleanup ON auth_requests(status, expires_at);`,
},
{
Version: 3,
Description: "Add is_admin, auth_events, rate_limit_events, project event caching",
SQL: `ALTER TABLE users ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0;
CREATE TABLE IF NOT EXISTS auth_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
auth_request_id TEXT NOT NULL,
email TEXT NOT NULL,
event_type TEXT NOT NULL,
metadata TEXT DEFAULT '{}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_auth_events_type ON auth_events(event_type);
CREATE INDEX IF NOT EXISTS idx_auth_events_email ON auth_events(email);
CREATE INDEX IF NOT EXISTS idx_auth_events_created ON auth_events(created_at);
CREATE TABLE IF NOT EXISTS rate_limit_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_id TEXT,
ip TEXT,
endpoint_class TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_rle_created ON rate_limit_events(created_at);
CREATE INDEX IF NOT EXISTS idx_rle_key ON rate_limit_events(key_id);
ALTER TABLE projects ADD COLUMN event_count INTEGER NOT NULL DEFAULT 0;
ALTER TABLE projects ADD COLUMN last_event_at DATETIME;`,
},
}
Migrations is the list of all server database migrations in order
Functions ¶
func EncodeCursor ¶ added in v0.32.0
func EncodeCursor(data CursorData) string
EncodeCursor encodes cursor data to an opaque base64 string.
func NewID ¶
func NewID() string
NewID generates a project ID (exported for callers that need to pre-generate IDs).
func NormalizeLimit ¶ added in v0.32.0
NormalizeLimit clamps limit to valid range.
Types ¶
type APIKey ¶
type APIKey struct {
ID string
UserID string
KeyPrefix string
Name string
Scopes string
ExpiresAt *time.Time
LastUsedAt *time.Time
CreatedAt time.Time
}
APIKey represents a stored API key (without the plaintext secret).
type AdminProject ¶ added in v0.32.0
type AdminProject struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
EventCount int `json:"event_count"`
LastEventAt *string `json:"last_event_at"`
MemberCount int `json:"member_count"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
DeletedAt *string `json:"deleted_at,omitempty"`
}
AdminProject represents a project with aggregate info for admin API.
type AdminProjectMember ¶ added in v0.32.0
type AdminProjectMember struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
InvitedBy string `json:"invited_by"`
CreatedAt string `json:"created_at"`
}
AdminProjectMember represents a project member with user email for admin view.
type AdminUser ¶ added in v0.32.0
type AdminUser struct {
ID string `json:"id"`
Email string `json:"email"`
IsAdmin bool `json:"is_admin"`
CreatedAt string `json:"created_at"`
ProjectCount int `json:"project_count"`
LastActivity *string `json:"last_activity"`
}
AdminUser represents a user with aggregate info for the admin API.
type AdminUserDetail ¶ added in v0.32.0
type AdminUserDetail struct {
AdminUser
Projects []UserProject `json:"projects"`
}
AdminUserDetail extends AdminUser with project membership details.
type AuthEvent ¶ added in v0.32.0
type AuthEvent struct {
ID int64 `json:"id"`
AuthRequestID string `json:"auth_request_id"`
Email string `json:"email"`
EventType string `json:"event_type"`
Metadata string `json:"metadata"`
CreatedAt string `json:"created_at"`
}
AuthEvent represents a row in the auth_events table.
type AuthRequest ¶
type AuthRequest struct {
ID string
Email string
DeviceCode string
UserCode string
Status string
UserID *string
APIKeyID *string
ExpiresAt time.Time
VerifiedAt *time.Time
CreatedAt time.Time
}
AuthRequest represents a device authorization request.
type CursorData ¶ added in v0.32.0
type CursorData struct {
ID string `json:"id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
Value string `json:"value,omitempty"`
}
CursorData holds the opaque cursor state.
func DecodeCursor ¶ added in v0.32.0
func DecodeCursor(cursor string) (CursorData, error)
DecodeCursor decodes an opaque cursor string back to CursorData.
type Membership ¶
type Membership struct {
ProjectID string
UserID string
Role string
InvitedBy string
CreatedAt time.Time
}
Membership represents a user's role in a project.
type PaginatedResult ¶ added in v0.32.0
type PaginatedResult[T any] struct { Data []T `json:"data"` NextCursor string `json:"next_cursor,omitempty"` HasMore bool `json:"has_more"` }
PaginatedResult holds a page of results with cursor information.
func PaginatedQuery ¶ added in v0.32.0
func PaginatedQuery[T any]( db *sql.DB, baseQuery string, args []any, limit int, cursor string, cursorColumn string, scanRow func(*sql.Rows) (T, string, error), ) (*PaginatedResult[T], error)
PaginatedQuery executes a paginated query using cursor-based pagination.
baseQuery is the SELECT query without ORDER BY or LIMIT clauses. args contains any existing WHERE clause parameters. cursor is the opaque cursor string from a previous PaginatedResult.NextCursor. cursorColumn is the column used for ordering and cursor comparison (e.g. "id", "created_at"). scanRow scans a single row and returns (item, cursorValue, error) where cursorValue is the value to encode in the next cursor.
The function fetches limit+1 rows to determine HasMore without a separate COUNT query.
type Project ¶
type Project struct {
ID string
Name string
Description string
EventCount int
LastEventAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
Project represents a sync project.
type RateLimitEvent ¶ added in v0.32.0
type RateLimitEvent struct {
ID int64
KeyID string // empty string if IP-based (nullable in DB)
IP string
EndpointClass string // auth, push, pull, other
CreatedAt string
}
RateLimitEvent represents a rate limit violation event.
type ServerDB ¶
type ServerDB struct {
// contains filtered or unexported fields
}
ServerDB wraps the server database connection
func Open ¶
Open opens the server database and runs any pending migrations. If the database file does not exist, it is created and initialized.
func (*ServerDB) AddMember ¶
func (db *ServerDB) AddMember(projectID, userID, role, invitedByUserID string) (*Membership, error)
AddMember adds a user to a project with the given role.
func (*ServerDB) AdminGetProject ¶ added in v0.32.0
func (db *ServerDB) AdminGetProject(id string) (*AdminProject, error)
AdminGetProject returns a single project with aggregate info including description.
func (*ServerDB) AdminGetUser ¶ added in v0.32.0
func (db *ServerDB) AdminGetUser(id string) (*AdminUserDetail, error)
AdminGetUser returns a single user with aggregate info and project memberships.
func (*ServerDB) AdminListProjectMembers ¶ added in v0.32.0
func (db *ServerDB) AdminListProjectMembers(projectID string) ([]AdminProjectMember, error)
AdminListProjectMembers returns all members of a project with user email.
func (*ServerDB) AdminListProjects ¶ added in v0.32.0
func (db *ServerDB) AdminListProjects(query string, includeDeleted bool, limit int, cursor string) (*PaginatedResult[AdminProject], error)
AdminListProjects returns a paginated list of all projects with aggregate counts.
func (*ServerDB) AdminListUsers ¶ added in v0.32.0
func (db *ServerDB) AdminListUsers(query string, limit int, cursor string) (*PaginatedResult[AdminUser], error)
AdminListUsers returns a paginated list of users with aggregate counts. The query parameter filters by email (LIKE match).
func (*ServerDB) Authorize ¶
Authorize checks that the user has at least the required role in the project.
func (*ServerDB) CanDeleteProject ¶
CanDeleteProject checks if the user can delete the project (requires owner role).
func (*ServerDB) CanManageMembers ¶
CanManageMembers checks if the user can manage members (requires owner role).
func (*ServerDB) CanPullEvents ¶
CanPullEvents checks if the user can pull events (requires reader role).
func (*ServerDB) CanPushEvents ¶
CanPushEvents checks if the user can push events (requires writer role).
func (*ServerDB) CanViewProject ¶
CanViewProject checks if the user can view the project (requires reader role).
func (*ServerDB) CleanupAuthEvents ¶ added in v0.32.0
CleanupAuthEvents deletes auth events older than the given duration. Returns the number of rows deleted.
func (*ServerDB) CleanupExpiredAuthRequests ¶
CleanupExpiredAuthRequests marks pending auth requests past their expiry as expired.
func (*ServerDB) CleanupRateLimitEvents ¶ added in v0.32.0
CleanupRateLimitEvents deletes events older than the given duration. Returns the number of rows deleted.
func (*ServerDB) CompleteAuthRequest ¶
func (db *ServerDB) CompleteAuthRequest(deviceCode string) (*AuthRequest, error)
CompleteAuthRequest transitions a verified auth request to used and returns it. Returns nil if the request is not in verified status.
func (*ServerDB) CountAdmins ¶ added in v0.32.0
CountAdmins returns the number of users with admin privileges.
func (*ServerDB) CountMembers ¶ added in v0.32.0
CountMembers returns the total number of memberships.
func (*ServerDB) CountProjects ¶ added in v0.32.0
CountProjects returns the total number of non-deleted projects.
func (*ServerDB) CountUsers ¶ added in v0.32.0
CountUsers returns the total number of users.
func (*ServerDB) CreateAuthRequest ¶
func (db *ServerDB) CreateAuthRequest(email string) (*AuthRequest, error)
CreateAuthRequest creates a new device auth request for the given email.
func (*ServerDB) CreateProject ¶
CreateProject creates a new project and adds the owner as a member in a single transaction.
func (*ServerDB) CreateProjectWithID ¶
func (db *ServerDB) CreateProjectWithID(id, name, description, ownerUserID string) (*Project, error)
CreateProjectWithID creates a new project using a pre-generated ID and adds the owner as a member.
func (*ServerDB) CreateUser ¶
CreateUser inserts a new user with the given email (lowercased). The first user created is automatically made an admin.
func (*ServerDB) ForceExpireAuthRequestForTest ¶
ForceExpireAuthRequestForTest forces an auth request's expiry time (test-only helper).
func (*ServerDB) GenerateAPIKey ¶
func (db *ServerDB) GenerateAPIKey(userID, name, scopes string, expiresAt *time.Time) (string, *APIKey, error)
GenerateAPIKey creates a new API key for the given user. Returns the plaintext key (shown once) and the stored APIKey record.
func (*ServerDB) GetAuthRequestByDeviceCode ¶
func (db *ServerDB) GetAuthRequestByDeviceCode(deviceCode string) (*AuthRequest, error)
GetAuthRequestByDeviceCode returns the auth request with the given device code, or nil.
func (*ServerDB) GetAuthRequestByUserCode ¶
func (db *ServerDB) GetAuthRequestByUserCode(userCode string) (*AuthRequest, error)
GetAuthRequestByUserCode returns the pending, non-expired auth request with the given user code, or nil.
func (*ServerDB) GetMembership ¶
func (db *ServerDB) GetMembership(projectID, userID string) (*Membership, error)
GetMembership returns a user's membership in a project, or nil if not found.
func (*ServerDB) GetPendingExpiredAuthRequests ¶ added in v0.32.0
func (db *ServerDB) GetPendingExpiredAuthRequests() ([]AuthRequest, error)
GetPendingExpiredAuthRequests returns pending auth requests that have expired, for use when logging "expired" auth events before cleanup marks them.
func (*ServerDB) GetProject ¶
GetProject returns a project by ID. If includeSoftDeleted is false, soft-deleted projects are excluded.
func (*ServerDB) GetProjectEventCount ¶ added in v0.32.0
GetProjectEventCount returns the cached event count and last event timestamp for a project.
func (*ServerDB) GetSyncCursor ¶
func (db *ServerDB) GetSyncCursor(projectID, clientID string) (*SyncCursor, error)
GetSyncCursor returns the sync cursor for a project/client pair, or nil if not found.
func (*ServerDB) GetUserByEmail ¶
GetUserByEmail returns the user with the given email (case-insensitive), or nil if not found.
func (*ServerDB) GetUserByID ¶
GetUserByID returns the user with the given ID, or nil if not found.
func (*ServerDB) InsertAuthEvent ¶ added in v0.32.0
InsertAuthEvent inserts an auth event row.
func (*ServerDB) InsertRateLimitEvent ¶ added in v0.32.0
InsertRateLimitEvent inserts a rate limit violation event. keyID may be empty for IP-based rate limiting (stored as NULL).
func (*ServerDB) IsUserAdmin ¶ added in v0.32.0
IsUserAdmin returns whether the user with the given ID is an admin.
func (*ServerDB) ListAPIKeys ¶
ListAPIKeys returns all API keys for a user (without secrets).
func (*ServerDB) ListAPIKeysForUser ¶ added in v0.32.0
ListAPIKeysForUser returns all API keys for a user without the key_hash.
func (*ServerDB) ListMembers ¶
func (db *ServerDB) ListMembers(projectID string) ([]*Membership, error)
ListMembers returns all members of a project.
func (*ServerDB) ListProjectsForUser ¶
ListProjectsForUser returns all non-deleted projects the user is a member of.
func (*ServerDB) ListSyncCursorsForProject ¶ added in v0.32.0
func (db *ServerDB) ListSyncCursorsForProject(projectID string) ([]SyncCursor, error)
ListSyncCursorsForProject returns all sync cursors for a project.
func (*ServerDB) QueryAuthEvents ¶ added in v0.32.0
func (db *ServerDB) QueryAuthEvents(eventType, email, from, to string, limit int, cursor string) (*PaginatedResult[AuthEvent], error)
QueryAuthEvents queries auth events with optional filters and cursor-based pagination. Filters: eventType (exact match), email (LIKE), from/to (created_at range).
func (*ServerDB) QueryRateLimitEvents ¶ added in v0.32.0
func (db *ServerDB) QueryRateLimitEvents(keyID, ip, from, to string, limit int, cursor string) (*PaginatedResult[RateLimitEvent], error)
QueryRateLimitEvents queries rate limit events with optional filters. Filters: keyID (exact), ip (exact), from/to (created_at range, RFC3339 or datetime). Uses cursor-based pagination via the PaginatedQuery helper.
func (*ServerDB) RemoveMember ¶
RemoveMember removes a user from a project. Fails if removing the user would leave the project with no owners.
func (*ServerDB) RevokeAPIKey ¶
RevokeAPIKey deletes an API key, only if owned by the given user.
func (*ServerDB) RunMigrations ¶
RunMigrations runs any pending database migrations.
func (*ServerDB) SetAuthRequestAPIKey ¶
SetAuthRequestAPIKey sets the API key ID on an auth request.
func (*ServerDB) SetEmailVerified ¶
SetEmailVerified marks the user's email as verified.
func (*ServerDB) SetUserAdmin ¶ added in v0.32.0
SetUserAdmin sets or clears the admin flag for a user identified by email.
func (*ServerDB) SoftDeleteProject ¶
SoftDeleteProject marks a project as deleted.
func (*ServerDB) UpdateMemberRole ¶
UpdateMemberRole changes a member's role.
func (*ServerDB) UpdateProject ¶
UpdateProject updates a project's name and description.
func (*ServerDB) UpdateProjectEventCount ¶ added in v0.32.0
func (db *ServerDB) UpdateProjectEventCount(projectID string, newEvents int, lastEventAt time.Time) error
UpdateProjectEventCount atomically increments event_count and updates last_event_at.
func (*ServerDB) UpsertSyncCursor ¶
UpsertSyncCursor creates or updates a sync cursor for a project/client pair.
func (*ServerDB) VerifyAPIKey ¶
VerifyAPIKey checks a plaintext key against stored hashes. Returns the matching APIKey and associated User, or an error.
func (*ServerDB) VerifyAuthRequest ¶
VerifyAuthRequest marks a pending auth request as verified with the given user ID.
type SyncCursor ¶
SyncCursor tracks a client's sync position in a project.
type User ¶
type User struct {
ID string
Email string
EmailVerifiedAt *time.Time
IsAdmin bool
CreatedAt time.Time
UpdatedAt time.Time
}
User represents a registered user.
type UserProject ¶ added in v0.32.0
type UserProject struct {
ProjectID string `json:"project_id"`
Name string `json:"name"`
Role string `json:"role"`
}
UserProject represents a project membership for a user.