Data Contracts
Data-Contracts.mdData Contracts
Big Rule
runtime data lives under /data.
repo intent:
/datais not supposed to be versioned normally.gitignoreignores/data/**/*.rsyncignoreexcludes/data/**from deployment sync
so local/dev/prod data has to be managed separately.
data/accounts/
accounts.json
expected top-level shape:
{
"accounts": [
{
"username": "string",
"name": "string",
"password": "bcrypt-hash or empty",
"isAdmin": true,
"mustResetPassword": false,
"discordUserId": "optional discord snowflake string",
"allowedPages": ["feed", "journal", "comments", "chat"],
"bookmarks": ["2026-01-01_12-00-00", "journal:12"],
"theme": "default|classic|theme-id",
"glowIntensity": "none|low|medium|high",
"mobileFriendlyView": true,
"onekoEnabled": true,
"colors": {
"bg": "#RRGGBB",
"fg": "#RRGGBB",
"border": "#RRGGBB",
"subtle": "#RRGGBB",
"links": "#RRGGBB"
}
}
]
}
notes:
- extra unknown keys can exist and are preserved by
account/admin/edit - bookmarks are the current source of truth for logged-in users
- bookmark ids currently use raw feed ids and
journal:{id}; legacynewsletter:{id}values can exist but are ignored theme: defaultis blackprint and uses the base template plus/style.css;theme: classicenables savedcolors; any other valid value refers to a/themes/{theme-id}.jsonfile- legacy
blackprintnormalizes todefault,customnormalizes toclassic, andnewsprintnormalizes towhiteprint mustResetPasswordis used by the shared session bootstrap to force first-login password changesdiscordUserIdlinks a site account to a Discord member for bot DMs and notificationsallowedPagescurrently includes functional grants likefeed,journal,comments, andchat
data/chat/
one-time private conversations live as individual encrypted JSON envelopes:
{
"version": 1,
"cipher": "aes-256-gcm",
"nonce": "base64",
"tag": "base64",
"ciphertext": "base64"
}
notes:
- each file is named
{conversationId}.json; new chat ids are 9 lowercase letters/numbers - legacy 32-character lowercase hex ids are still accepted so older active links do not break
- decrypted payloads contain conversation metadata, the recipient label in
name, the recipient cookie hash, and message records - messages may include an
attachmentobject with encrypted blob metadata:id,name,mime, andsize; image/audio/video attachments are served inline through the authorized chat route so they can render or play in the chat UI - messages may include
replyTowith another message id, plusreactionskeyed by valid emoji sequences with active viewer roles such asmanagerorparticipant - conversations may include
participantUsernamewhen a logged-in account claims the invite, orparticipantHashwhen an anonymous browser cookie claims it; the first-open popup copy changes based on that claim type - conversations may include
recipientIntroSeenAtonce the recipient has seen the first-open security/help popup - recipient cookies are HttpOnly and scoped to
/chat - the first non-manager account or anonymous browser to open
/chat/{conversationId}claims the recipient slot - account-linked recipients can delete their own active chat; anonymous cookie-linked recipients cannot
- admins and accounts with
allowedPagescontainingchatcan create, view, and delete conversations without claiming the recipient slot - deleting a conversation unlinks the encrypted JSON file immediately
- encryption uses
FRIDG3_CHAT_KEYwhen set; otherwise the app createsdata/chat/.chat_key - lightweight presence indicators use sidecar files under
data/chat/.presence/{conversationId}.json; current entries storelastSeen,active, and a short-livedtypingUntil, while older timestamp-only entries are still readable - attachments are encrypted AES-256-GCM envelopes under
data/chat/.attachments/{conversationId}/; they are served only through the authorized chat route and are deleted with the conversation - attachment uploads are capped at 8 MB
/themes/
theme metadata lives as JSON files directly under /themes.
{
"name": "Theme Name",
"html": "template-file.html",
"css": "stylesheet-file.css"
}
notes:
- the metadata filename is the saved theme id, for example
/themes/cool.jsonbecomescool nameis the label shown in/settingshtmlandcssmust be relative paths in/themes/lib, for exampleaero/aero.htmlandaero/aero.css- theme asset paths cannot be absolute, contain
.., or use characters outside letters, numbers,.,_,-, and/ - desktop rendering uses both themed HTML and CSS
- mobile rendering keeps
template_mobile.htmland only swaps the CSS
login_attempts.json
- map of client IP -> unix timestamp array
- used for login throttling
data/feed/
feed post format:
@usernameYYYY-MM-DD HH:MM:SS- body text / BBCode
other file:
index.tomlis generated by/feed/index.php
feed bodies can include public voice notes as BBCode:
[audio=/data/audio/voice/example.m4a][name:voice-note.m4a]
voice notes are created from temporary [voice:N] editor placeholders, verified at upload time, transcoded to small mono .m4a files, and stored under data/audio/voice/.
data/feed/replies/
per-post replies live in {postId}.json files shaped roughly like:
{
"replies": [
{
"id": "20260413153000_deadbeef",
"username": "toast",
"date": "2026-04-13 15:30:00",
"body": "reply body with BBCode"
}
]
}
notes:
- reply ids are generated on write; older data may be normalized into
legacy_*ids at read time - reply bodies can contain image BBCode that points at
/data/images/* - new reply bodies can also contain voice note audio BBCode that points at
/data/audio/voice/*
data/journal/
published journal post:
YYYY-MM-DD- title
- description
- trusted HTML body
draft format:
USER:<username>- title
- description
- optional
FORMAT:html - draft body
without FORMAT:html, preview treats the body as BBCode. with it, preview treats the body as raw HTML.
data/guestbook/
entry format:
- timestamp
- display name
- message body
plus:
ip_index.jsonfor one-post-per-IP ownership tracking
data/images/
- uploaded images used across feed, journal, and gallery content
- expected web path is
/data/images/<filename>
data/music/
artist folders currently include:
frdg3cactile
album JSON shape:
{
"album_name": "string",
"album_caption": "string",
"album_type": "Album|EP|Single|Remix|...",
"album_art": "/data/images/example.jpg",
"album_art_directory": "/data/images/example.jpg",
"order": 6,
"songs": [
{ "name": "Track", "directory": "/data/audio/file.wav" }
]
}
album_art_directory is preferred by current code.
data/audio/
- track files referenced by music metadata
- also used by shared playback features
data/audio/voice/stores public feed voice notes as compressed.m4afiles
data/contact/
- private contact submissions as
{YYYYMMDDHHMMSS}_{random}.json - each submission stores
id,createdAt, hashed IP, user agent, name, email, message, notification channel id, and optionalnotifyError rate_limits.jsonstores hashed client IP keys mapped to recent submission timestamps for throttling- nginx blocks direct web access to this directory; submissions are only shown through the admin-only
/contact?dashboard=1route
data/mdpaste/
- temporary markdown paste records as
{id}.json - ids are 16 lowercase hex characters
- records expire after 30 days and are cleaned up opportunistically on create/view
- unencrypted records store a
markdownstring - encrypted records store only AES-256-GCM ciphertext plus PBKDF2-SHA256 salt/nonce/tag metadata; the password is never stored
hard_breakscontrols whether single paragraph newlines render as<br>instead of spaces
data/etc/
wip
- plain text maintenance flag
webhooks.json
used key:
{
"discord_feed": "https://discord.com/api/webhooks/..."
}
toast.json
expected shape:
{
"bot": { "token": "...", "client_id": "...", "status": "online|offline" },
"stream": { "url": "http(s)://...", "name": "..." },
"channel": { "id": "...", "name": "..." },
"features": { "auto_play": true, "loop": true }
}
toast-updates.json
- array of timestamped bot status entries
toast-feed-notify-state.json
- internal bot dedupe state for sent feed mention/reply notifications
- stores which feed mentions and replies have already triggered DMs
toast-dm-history.json
- tracked inbound/outbound DM threads used by
/others/toast-discord-bot/messages - stores per-user profile snapshot data plus message history
contact notification endpoint
- the toast bot exposes localhost-only
POST /contact/notifyon127.0.0.1:8765 /contactcalls it after saving a submission- toast sends the alert to Discord channel
1503931489560301609
off-topic-archive.json
- Discord export blob used by the archive viewer
page_views.json
shape is roughly:
{
"pages": {
"/": {
"count": 12,
"visitors": {
"<sha256>": 1730931224
}
}
},
"updated_at": "2026-03-02T00:00:00Z"
}
data/downloads/
- downloadable binaries, archives, presets, and similar files linked from the site