Documentation
¶
Overview ¶
Package pg implements the storage layer for the application using a PostgreSQL database. It provides concrete implementations of the service layer's storage interfaces.
This package follows a "Public/Private Method" pattern:
- Public, exported methods match the service interfaces. Their primary role is to manage database transactions (begin, commit, rollback) and call the corresponding private methods.
- Private, unexported methods contain the core database logic. They accept a `Querier` interface, making them transaction-agnostic and highly testable.
This design keeps the service layer clean and unaware of the database implementation details while allowing for robust, atomic, and testable database operations.
Index ¶
- func PartitionName(shortName domain.BoardShortName, table string) string
- func ViewTableName(shortName domain.BoardShortName) string
- type MsgKey
- type Querier
- type Storage
- func (s *Storage) BlacklistUser(userId domain.UserId, reason string, blacklistedBy domain.UserId) error
- func (s *Storage) Cleanup() error
- func (s *Storage) ConfirmationData(emailHash []byte) (domain.ConfirmationData, error)
- func (s *Storage) CountActiveInvites(userId domain.UserId) (int, error)
- func (s *Storage) CreateBoard(creationData domain.BoardCreationData) error
- func (s *Storage) CreateMessage(creationData domain.MessageCreationData, attachments domain.Attachments) (domain.MsgId, error)
- func (s *Storage) CreateThread(creationData domain.ThreadCreationData, maxThreadCount *int) (domain.ThreadId, time.Time, error)
- func (s *Storage) DeleteBoard(shortName domain.BoardShortName) error
- func (s *Storage) DeleteConfirmationData(emailHash []byte) error
- func (s *Storage) DeleteInviteCode(codeHash string) error
- func (s *Storage) DeleteInvitesByUser(userId domain.UserId) error
- func (s *Storage) DeleteMessage(board domain.BoardShortName, threadId domain.ThreadId, id domain.MsgId) error
- func (s *Storage) DeleteOrphanedFileRecords() (int64, error)
- func (s *Storage) DeleteThread(board domain.BoardShortName, id domain.ThreadId) error
- func (s *Storage) DeleteUser(emailHash []byte) error
- func (s *Storage) GetActiveBoards(interval time.Duration) ([]domain.Board, error)
- func (s *Storage) GetAllFilePaths() ([]string, error)
- func (s *Storage) GetBlacklistedUsersWithDetails(limit, offset int) ([]domain.BlacklistEntry, error)
- func (s *Storage) GetBoard(shortName domain.BoardShortName, page int) (domain.Board, error)
- func (s *Storage) GetBoardLastModified(shortName domain.BoardShortName) (time.Time, error)
- func (s *Storage) GetBoards() ([]domain.BoardMetadata, error)
- func (s *Storage) GetBoardsWithPermissions() (map[string][]string, error)
- func (s *Storage) GetInvitesByUser(userId domain.UserId, limit, offset int) ([]domain.InviteCode, error)
- func (s *Storage) GetMessage(board domain.BoardShortName, threadId domain.ThreadId, id domain.MsgId) (domain.Message, error)
- func (s *Storage) GetRecentlyBlacklistedUsers(since time.Time) ([]domain.UserId, error)
- func (s *Storage) GetReferralActionStats() ([]domain.ReferralActionStats, error)
- func (s *Storage) GetThread(board domain.BoardShortName, id domain.ThreadId, page int) (domain.Thread, error)
- func (s *Storage) GetThreadLastModified(board domain.BoardShortName, id domain.ThreadId) (time.Time, error)
- func (s *Storage) GetUserMessages(userId domain.UserId, limit int) ([]domain.Message, error)
- func (s *Storage) InviteCodeByHash(codeHash string) (domain.InviteCode, error)
- func (s *Storage) IsUserBlacklisted(userId domain.UserId) (bool, error)
- func (s *Storage) MarkInviteUsed(codeHash string, usedBy domain.UserId) error
- func (s *Storage) Ping(ctx context.Context) error
- func (s *Storage) SaveConfirmationData(data domain.ConfirmationData) error
- func (s *Storage) SaveInviteCode(invite domain.InviteCode) error
- func (s *Storage) SaveReferralAction(source, action, ip string) error
- func (s *Storage) SaveUser(user domain.User) (domain.UserId, error)
- func (s *Storage) StartPeriodicViewRefresh(ctx context.Context, refreshInterval, activityWindow time.Duration)
- func (s *Storage) TogglePinnedStatus(board domain.BoardShortName, threadId domain.ThreadId) (bool, error)
- func (s *Storage) UnblacklistUser(userId domain.UserId) error
- func (s *Storage) UpdatePassword(emailHash []byte, newPasswordHash domain.Password) error
- func (s *Storage) User(emailHash []byte) (domain.User, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func PartitionName ¶
func PartitionName(shortName domain.BoardShortName, table string) string
PartitionName is a convenience wrapper around shared storage's PartitionName. It generates a properly quoted and escaped partition table name for use in SQL queries. Example: ("tech", "messages") -> `"messages_tech"`
func ViewTableName ¶
func ViewTableName(shortName domain.BoardShortName) string
ViewTableName is a convenience wrapper around shared storage's ViewTableName. It generates a properly quoted and escaped materialized view name for use in SQL queries. Example: ("tech") -> `"board_preview_tech"`
Types ¶
type MsgKey ¶
MsgKey is a composite key for identifying a message within a board. Since message IDs are per-thread sequential, we need both thread_id and msg_id.
type Querier ¶
type Querier = sharedstorage.Querier
Querier is an alias to the shared Querier interface. It abstracts database operations and is satisfied by *sql.DB and *sql.Tx. This interface is defined in shared/storage/pg and used throughout the application.
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage is the central struct for the PostgreSQL persistence layer. It holds the database connection pool and application configuration, and acts as the receiver for all storage methods.
func New ¶
New creates and returns a new Storage instance. It establishes a connection to the database and starts any necessary background processes, such as the materialized view refresher. This function is the main entry point for initializing the persistence layer.
func (*Storage) BlacklistUser ¶
func (s *Storage) BlacklistUser(userId domain.UserId, reason string, blacklistedBy domain.UserId) error
BlacklistUser adds a user to the blacklist. This is the public entry point that wraps the operation in a transaction.
func (*Storage) Cleanup ¶
Cleanup gracefully closes the database connection pool. It should be called during application shutdown.
func (*Storage) ConfirmationData ¶
func (s *Storage) ConfirmationData(emailHash []byte) (domain.ConfirmationData, error)
ConfirmationData is a public, read-only method to retrieve confirmation data.
func (*Storage) CountActiveInvites ¶
CountActiveInvites returns the number of active (unused, unexpired) invites for a user
func (*Storage) CreateBoard ¶
func (s *Storage) CreateBoard(creationData domain.BoardCreationData) error
CreateBoard is the public entry point for creating a new board. It wraps the entire board creation process, including metadata insertion, partition creation, and view creation, within a single atomic transaction. This guarantees that a board is either fully created or not created at all.
func (*Storage) CreateMessage ¶
func (s *Storage) CreateMessage(creationData domain.MessageCreationData, attachments domain.Attachments) (domain.MsgId, error)
CreateMessage serves as the public entry point for creating a new message. It is responsible for wrapping the core message creation logic in a single, atomic database transaction. This ensures that all related database operations (updating board/thread metadata, inserting the message, attachments, and replies) either succeed together or fail together, maintaining data integrity.
func (*Storage) CreateThread ¶
func (s *Storage) CreateThread(creationData domain.ThreadCreationData, maxThreadCount *int) (domain.ThreadId, time.Time, error)
CreateThread creates a thread and optionally enforces the max thread count per board using pg_advisory_xact_lock to prevent race conditions. When maxThreadCount is non-nil, it acquires a board-level advisory lock, creates the thread, and deletes the oldest non-pinned threads if the board exceeds the limit.
func (*Storage) DeleteBoard ¶
func (s *Storage) DeleteBoard(shortName domain.BoardShortName) error
DeleteBoard is the public entry point for deleting a board. It manages the transaction for this destructive operation, ensuring that the board's materialized view, all its table partitions, and its metadata are removed atomically.
func (*Storage) DeleteConfirmationData ¶
DeleteConfirmationData is the public entry point for removing used or expired confirmation data.
func (*Storage) DeleteInviteCode ¶
DeleteInviteCode deletes an invite code by its hash
func (*Storage) DeleteInvitesByUser ¶
DeleteInvitesByUser deletes all unused invite codes created by a user
func (*Storage) DeleteMessage ¶
func (s *Storage) DeleteMessage(board domain.BoardShortName, threadId domain.ThreadId, id domain.MsgId) error
DeleteMessage is the public entry point for deleting a message. It manages the transaction for this operation, ensuring that the board's last activity is updated and the message is deleted atomically. The cascading deletion of related attachments and replies is handled by the database schema.
func (*Storage) DeleteOrphanedFileRecords ¶
DeleteOrphanedFileRecords deletes file records not referenced by any attachment. Returns the number of records deleted. This is used by the garbage collector to clean up orphaned database records.
func (*Storage) DeleteThread ¶
DeleteThread is the public entry point for deleting a thread. It wraps the core deletion logic in a transaction to ensure atomicity. The database schema's foreign key constraints will cascade the delete from the thread to all of its contained messages, attachments, and replies.
func (*Storage) DeleteUser ¶
DeleteUser is the public entry point for deleting a user account. It wraps the deletion in a transaction. The database schema's ON DELETE CASCADE constraints will handle cleaning up related data (e.g., confirmation data).
func (*Storage) GetActiveBoards ¶
GetActiveBoards is a public, read-only method used by the view refresh background process to find boards with recent activity.
func (*Storage) GetAllFilePaths ¶
GetAllFilePaths returns all file paths stored in the database. This is used by the garbage collector to identify orphaned files. Returns both original file paths and thumbnail paths.
func (*Storage) GetBlacklistedUsersWithDetails ¶
func (s *Storage) GetBlacklistedUsersWithDetails(limit, offset int) ([]domain.BlacklistEntry, error)
GetBlacklistedUsersWithDetails retrieves blacklisted users with their full details (reason, blacklisted_at, blacklisted_by) for admin display purposes, with pagination.
func (*Storage) GetBoard ¶
GetBoard is a public, read-only method for fetching a single page of a board's content. It delegates directly to the internal method using the main database connection pool.
func (*Storage) GetBoardLastModified ¶
GetBoardLastModified returns the view_last_modified_at timestamp for a board. This reflects the last_activity_at value snapshotted after each materialized view refresh, ensuring the returned timestamp only covers changes the view has actually incorporated.
func (*Storage) GetBoards ¶
func (s *Storage) GetBoards() ([]domain.BoardMetadata, error)
GetBoards is a public, read-only method to fetch metadata for all boards.
func (*Storage) GetBoardsWithPermissions ¶
GetBoardsWithPermissions returns a map of board short names to their allowed email domains. Returns nil for boards without restrictions (public boards).
func (*Storage) GetInvitesByUser ¶
func (s *Storage) GetInvitesByUser(userId domain.UserId, limit, offset int) ([]domain.InviteCode, error)
GetInvitesByUser returns invite codes created by a user, with pagination.
func (*Storage) GetMessage ¶
func (s *Storage) GetMessage(board domain.BoardShortName, threadId domain.ThreadId, id domain.MsgId) (domain.Message, error)
GetMessage is a read-only operation that fetches a complete message, including its attachments and replies. Since it doesn't modify data, it doesn't need to create its own transaction. It can use the main database connection pool (s.db) as the Querier, allowing for concurrent reads.
func (*Storage) GetRecentlyBlacklistedUsers ¶
GetRecentlyBlacklistedUsers fetches all user IDs that were blacklisted after the specified time. This is used for cache updates with TTL-based filtering.
func (*Storage) GetReferralActionStats ¶ added in v0.3.2
func (s *Storage) GetReferralActionStats() ([]domain.ReferralActionStats, error)
func (*Storage) GetThread ¶
func (s *Storage) GetThread(board domain.BoardShortName, id domain.ThreadId, page int) (domain.Thread, error)
GetThread is the public entry point for fetching a full thread, including all of its messages, replies, and attachments. It decides whether to use the optimized single-page fetch or the paginated fetch based on thread size. The page parameter controls pagination (1-based). Page 0 or 1 returns the first page.
func (*Storage) GetThreadLastModified ¶
func (s *Storage) GetThreadLastModified(board domain.BoardShortName, id domain.ThreadId) (time.Time, error)
GetThreadLastModified returns only the last_modified_at timestamp for a thread.
func (*Storage) GetUserMessages ¶
GetUserMessages fetches user's last N messages across all boards. Returns FULLY enriched domain.Message objects with Author, Attachments, Replies.
func (*Storage) InviteCodeByHash ¶
func (s *Storage) InviteCodeByHash(codeHash string) (domain.InviteCode, error)
InviteCodeByHash fetches an invite code by its hash
func (*Storage) IsUserBlacklisted ¶
IsUserBlacklisted checks if a specific user is currently blacklisted. This is a read-only operation used for direct DB checks (e.g., at login).
func (*Storage) MarkInviteUsed ¶
MarkInviteUsed marks an invite code as used by a specific user
func (*Storage) Ping ¶
Ping checks if the database connection is alive. Used by health check endpoints to verify database connectivity.
func (*Storage) SaveConfirmationData ¶
func (s *Storage) SaveConfirmationData(data domain.ConfirmationData) error
SaveConfirmationData is the public entry point for storing password reset or account confirmation tokens.
func (*Storage) SaveInviteCode ¶
func (s *Storage) SaveInviteCode(invite domain.InviteCode) error
SaveInviteCode saves a new invite code to the database
func (*Storage) SaveReferralAction ¶ added in v0.3.2
func (*Storage) SaveUser ¶
SaveUser is the public entry point for creating a new user. It wraps the core logic in a transaction to ensure the operation is atomic.
func (*Storage) StartPeriodicViewRefresh ¶
func (s *Storage) StartPeriodicViewRefresh(ctx context.Context, refreshInterval, activityWindow time.Duration)
StartPeriodicViewRefresh initiates a background goroutine that periodically refreshes materialized views for active boards. This ensures board preview data stays up-to-date without blocking user requests.
The refresh process:
- Runs on a ticker (refreshInterval)
- Identifies boards with recent activity within the activityWindow period
- Concurrently refreshes each active board's materialized view
- Stops gracefully when the context is canceled
The activityWindow should be larger than refreshInterval to ensure boards don't "fall out" of the active window between refresh cycles. For example, with a 5s refresh and 30s activity window, a board gets up to 6 refresh opportunities after each post.
This is called once during application initialization from New().
func (*Storage) TogglePinnedStatus ¶
func (s *Storage) TogglePinnedStatus(board domain.BoardShortName, threadId domain.ThreadId) (bool, error)
TogglePinnedStatus is the public entry point for toggling a thread's pinned status. It wraps the update in a transaction and returns the new pinned status.
func (*Storage) UnblacklistUser ¶
UnblacklistUser removes a user from the blacklist. This is the public entry point that wraps the operation in a transaction.
func (*Storage) UpdatePassword ¶
UpdatePassword is the public entry point for changing a user's password. It manages the transaction for this security-sensitive operation.