-
Notifications
You must be signed in to change notification settings - Fork 0
Data Contracts
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.
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 changes -
discordUserIdlinks a site account to a Discord member for bot DMs and notifications -
allowedPagescurrently includes functional grants likefeed,journal,comments, andchat
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
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/settings -
htmlandcssmust 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
- map of client IP -> unix timestamp array
- used for login throttling
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/.
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/*
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.
entry format:
- timestamp
- display name
- message body
plus:
-
ip_index.jsonfor one-post-per-IP ownership tracking
- uploaded images used across feed, journal, and gallery content
- expected web path is
/data/images/<filename>
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.
- track files referenced by music metadata
- also used by shared playback features
-
data/audio/voice/stores public feed voice notes as compressed.m4afiles
- 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
- 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
- plain text maintenance flag
used key:
{
"discord_feed": "https://discord.com/api/webhooks/..."
}expected shape:
{
"bot": { "token": "...", "client_id": "...", "status": "online|offline" },
"stream": { "url": "http(s)://...", "name": "..." },
"channel": { "id": "...", "name": "..." },
"features": { "auto_play": true, "loop": true }
}- array of timestamped bot status entries
- internal bot dedupe state for sent feed mention/reply notifications
- stores which feed mentions and replies have already triggered DMs
- tracked inbound/outbound DM threads used by
/others/toast-discord-bot/messages - stores per-user profile snapshot data plus message history
- 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
- Discord export blob used by the archive viewer
shape is roughly:
{
"pages": {
"/": {
"count": 12,
"visitors": {
"<sha256>": 1730931224
}
}
},
"updated_at": "2026-03-02T00:00:00Z"
}- downloadable binaries, archives, presets, and similar files linked from the site