minimost.common

minimost.common

Shared path helpers and per-user database initialisation.

This module provides two things:

  • Path resolution — a single DB_DIR constant and user_db_path() so every other module refers to the same on-disk location without duplicating the path logic.

  • Schema bootstrapinit_user_db() creates (or opens) a user’s SQLite database and ensures the messages table exists with the full column set.

Module-level attributes

DB_DIRpathlib.Path

Absolute path to the users/ directory that stores all per-user SQLite database files. The directory is created lazily by init_user_db() the first time it is called.

minimost.common.user_db_path(username: str) Path[source]

Return the absolute filesystem path for a user’s SQLite database.

The path follows the pattern <project_root>/users/<username>.db. This function does not create the file or its parent directory — use init_user_db() for that.

Parameters:

username (str) – The account username. Must be a valid filename component (alphanumeric, hyphens, and underscores).

Returns:

Absolute path to the user’s .db file.

Return type:

pathlib.Path

Example:

path = user_db_path("alice")
# e.g. PosixPath('/srv/minimost/users/alice.db')
minimost.common._ensure_search_index(cur) None[source]

Create the trigram full-text index over messages.content.

Message search is a case-insensitive substring match. A plain content LIKE '%q%' cannot use an index (leading wildcard) and so scans the whole messages table on every keystroke — cost grows linearly with history. An FTS5 virtual table with the trigram tokenizer indexes every 3-character substring, so the identical substring query is answered from the index in well under a millisecond no matter how large the history grows.

The table is external-content (content='messages'): it stores only the trigram postings, not a second copy of the message text, and is kept in sync with messages by the three triggers created here. tokenize='trigram' needs SQLite ≥ 3.34 (2020), which ships with every supported CPython.

Idempotent — safe to call from every init_user_db(). The first time it runs on a database that already holds messages, it indexes the existing rows with a one-off 'rebuild'.

minimost.common.shared_db_path() Path[source]

Return the absolute path of the single shared message database.

MiniMost stores every message exactly once in a single shared database (users/messages.db) rather than copying it into a per-user file. The path is resolved from the live DB_DIR on each call so the test suite’s monkeypatched directory is honoured.

Returns:

Absolute path to messages.db.

Return type:

pathlib.Path

minimost.common.init_user_db(username: str | None = None)[source]

Backwards-compatible alias for init_messages_db().

Historically each account had its own database, initialised here. The data model is now a single shared database, so username is ignored and this simply ensures the shared schema exists. Kept as a named entry point because the signup and account-recovery flows still call it per account.

Parameters:

username – Ignored. Retained for call-site compatibility.

Returns:

None

minimost.common.init_messages_db()[source]

Create the shared message database and ensure its schema is current.

Idempotent — every table uses CREATE TABLE IF NOT EXISTS, so repeated calls are safe and cheap. Creates:

  • messages — one canonical row per message (referenced everywhere by its auto-increment id; there are no per-user copies, so edits/deletes/ reactions act on a single row).

  • messages_fts — the trigram substring search index (see _ensure_search_index()).

  • reactions — one row per (message, emoji, reactor), keyed by the real message_id rather than a timestamp.

  • read_state — a per-(user, channel) read watermark (last_read_ts). Unread counts and read receipts are both derived from it, so read state costs O(users × channels) rows instead of O(messages × users).

  • dm_hidden — per-(user, channel) hidden-DM markers.

WAL keeps readers (the 500 ms pollers) from blocking the single writer; auto_vacuum = INCREMENTAL reclaims space from retention/edits without the long global lock that FULL would take on a shared file.

Returns:

None

Messages Table Schema

The messages table created by minimost.common.init_user_db() has the following columns:

Column

Type

Description

id

INTEGER PK

Auto-increment primary key (differs across per-user databases).

channel

TEXT NOT NULL

Public channel name or DM identifier ("dm:user1:user2").

sender

TEXT NOT NULL

Username of the message author.

content

TEXT

Message text body. NULL for image-only messages.

content_type

TEXT

Always 'text'. Reserved for future media types.

filename

TEXT

UUID-based image filename. NULL for text-only messages.

ts

REAL NOT NULL

Unix timestamp (seconds, floating-point). Shared across all user copies of the same message.

edited

INTEGER (0/1)

Whether this message has been edited.

edited_ts

REAL

Timestamp of the most recent edit.

read

INTEGER (0/1)

Per-user read flag. Sender’s copy is inserted as read=0; seeds from minimost.auth._seed_channel_history() are read=1.

deleted

INTEGER (0/1)

Soft-delete flag.

deleted_ts

REAL

Timestamp of deletion.

reply_to_id

INTEGER FK

Foreign key to messages.id for threaded replies.

reactions

TEXT

Legacy column (unused). Reactions are in presence.db.

reactions_ts

REAL

Updated when a reaction is toggled; triggers polling pickup.

mentions

TEXT

Reserved for future @mention tracking.

metadata

TEXT

Reserved for future structured metadata.

client_msg_id

TEXT

Client-generated deduplication token.

expires_ts

REAL

Expiry timestamp for the associated upload file.