# Profile: avatar, about and timezone (contract between workers) The global rules from `docs/m1-interfaces.md` apply: latest dependencies in root `[workspace.dependencies]`, no unwrap in non-test code, clippy `-D warnings` clean, no inline styles and no JS. ## Storage: migration `crates/forge-core/migrations/0002_profile.sql` ```sql ALTER TABLE users ADD COLUMN about TEXT NOT NULL DEFAULT ''; ALTER TABLE users ADD COLUMN timezone TEXT NOT NULL DEFAULT 'UTC'; CREATE TABLE avatars ( user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, data BLOB NOT NULL, -- re-encoded PNG, 256x256 hash TEXT NOT NULL, -- hex sha256 of data (first 16 chars), used as ETag and as ?v= updated_at TEXT NOT NULL ); ``` Avatars live in the DB, not on disk, so the standby replica and the backups carry them automatically. Each one is about 30–80 KB. ## forge-core ```rust // model.rs: User gains pub about: String, // raw markdown, at most 2000 chars pub timezone: String, // IANA name, e.g. "Europe/Istanbul"; "UTC" by default // store pub async fn set_profile(&self, user_id: &str, about: &str, timezone: &str) -> Result<()>; pub async fn set_avatar(&self, user_id: &str, png: &[u8], hash: &str) -> Result<()>; // upsert pub async fn delete_avatar(&self, user_id: &str) -> Result<()>; pub async fn avatar_hash(&self, user_id: &str) -> Result>; pub async fn avatar(&self, user_id: &str) -> Result, String /*hash*/)>>; // tz.rs (jiff, latest; bundled tzdb so it doesn't depend on the host) pub fn validate_timezone(name: &str) -> Result<()>; // must be a known IANA name pub fn all_timezones() -> Vec; // sorted, for the ` that lists every timezone - every POST has CSRF - `GET /{username}`: the public profile page, showing: - avatar - display username - about, through `render_markdown(about, None)` - "Local time: 14:05 (UTC+03:00)" - repos visible to the viewer - 404 for an unknown user - Fixed routes (`/new`, `/login`, `/setup`, `/settings/*`, `/admin/*`, `/assets/*`, `/avatars/*`) must keep winning; reserved names guarantee no user owns those paths. - **Absolute times** (the `title`/absolute text on commit, commits, tree and refs pages) use the logged-in viewer's timezone via `tz::format_in_tz`, or UTC for anonymous viewers. Relative times stay as they are.