Index: src/routes/account.js
===================================================================
--- src/routes/account.js	(revision 2165b6d3d57ceaa8b5d1f70491868482149470cf)
+++ src/routes/account.js	(revision 7bc636b391c66ac399c33e54f7173a022c6a3cbd)
@@ -16,18 +16,16 @@
 import path from 'path';
 import fs from 'fs';
+import { fileURLToPath } from 'url';
 import bcrypt from 'bcryptjs';
 import multer from 'multer';
 import { v4 as uuid } from 'uuid';
 import db from '../config/database.js';
-import { getPrimarySite } from '../middleware/site.js';
 import { renderPage } from '../middleware/render.js';
-import OAuth from '../services/OAuthService.js';
-import { t } from '../services/i18n.js';
 import { requireAuth } from '../middleware/auth.js';
-import { toWebp } from '../services/ImageWebpService.js';
-import { SUPPORTED } from '../services/i18n.js';
-import { mediaDir } from '../config/paths.js';
 
-const AVATAR_DIR = mediaDir('AVATAR_PATH', 'avatars');
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const AVATAR_DIR = path.resolve(
+  process.env.AVATAR_PATH || path.join(__dirname, '..', '..', 'storage', 'media', 'avatars')
+);
 fs.mkdirSync(AVATAR_DIR, { recursive: true });
 
@@ -59,21 +57,12 @@
 router.get('/', requireAuth, (req, res) => {
   const account = db.prepare(`
-    SELECT id, username, email, role, bio, avatar_url, created_at, password_hash, google_sub, lang
+    SELECT id, username, email, role, bio, avatar_url, created_at
     FROM users WHERE id = ?
   `).get(req.session.user.id);
-  const hasPassword = !!(account && account.password_hash && account.password_hash !== '!google-oauth');
-  if (account) { delete account.password_hash; delete account.google_sub; } // don't leak to the view
 
-  const editableSite = ownedSite(req.session.user);
   renderPage(req, res, 'pages/account', {
     pageTitle: 'Account',
     bodyClass: 'on-special',
     account,
-    hasPassword,
-    editableSite,
-    // Display fallback: when you have no own account avatar, show your site's photo.
-    siteAvatar: editableSite ? editableSite.profile_photo : null,
-    // OAuth apps (C2S) this user has authorized, so they can revoke them here.
-    authorizations: OAuth.listAuthorizations(req.session.user.id),
     success: req.query.success || null,
     error: req.query.error || null,
@@ -81,95 +70,10 @@
 });
 
-// ==================== REVOKE AN OAUTH APP AUTHORIZATION ====================
-router.post('/oauth/revoke', requireAuth, (req, res) => {
-  const lang = req.session.lang || (req.session.user && req.session.user.lang) || 'nl';
-  const ok = OAuth.revokeAuthorization(req.session.user.id, req.body.token_hash);
-  const msg = ok ? t(lang, 'acct.oauth_revoked') : t(lang, 'acct.oauth_revoke_none');
-  res.redirect('/account?' + (ok ? 'success' : 'error') + '=' + encodeURIComponent(msg));
-});
-
-// ==================== PERSONAL INTERFACE LANGUAGE ====================
-// Saves the language choice on the account (persists across devices/sessions) and
-// also sets it in the session immediately so it takes effect right away.
-router.post('/lang', requireAuth, (req, res) => {
-  const code = SUPPORTED.includes(req.body.lang) ? req.body.lang : null;
-  if (code) {
-    db.prepare('UPDATE users SET lang = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(code, req.session.user.id);
-    req.session.user.lang = code;
-    req.session.lang = code;
-  }
-  res.redirect('/account?success=' + encodeURIComponent('Taal opgeslagen'));
-});
-
-// The site this user may edit from their account: their own site
-// (owner_id), or for a god the primary site. Null if nothing found.
-function ownedSite(user) {
-  if (!user) return null;
-  let site = db.prepare('SELECT id, title, tagline, slug, owner_id, profile_photo FROM sites WHERE owner_id = ? ORDER BY created_at LIMIT 1').get(user.id);
-  if (!site && user.role === 'god') {
-    site = getPrimarySite(); // primary/main site as fallback
-  }
-  return site || null;
-}
-
-// ==================== UPDATE SITE-NAAM (eigenaar) ====================
-router.post('/site', requireAuth, (req, res) => {
-  const site = ownedSite(req.session.user);
-  if (!site) return res.redirect('/account?error=' + encodeURIComponent('Geen site om te bewerken.'));
-  if (site.owner_id !== req.session.user.id && req.session.user.role !== 'god') {
-    return res.redirect('/account?error=' + encodeURIComponent('Geen rechten om deze site te bewerken.'));
-  }
-  const title = (req.body.site_title || '').toString().slice(0, 200).trim();
-  if (!title) return res.redirect('/account?error=' + encodeURIComponent('Site-naam mag niet leeg zijn.'));
-  const tagline = (req.body.site_tagline || '').toString().slice(0, 200).trim();
-  db.prepare('UPDATE sites SET title = ?, tagline = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
-    .run(title, tagline || null, site.id);
-  res.redirect('/account?success=' + encodeURIComponent('Site-naam bijgewerkt'));
-});
-
 // ==================== UPDATE BIO ====================
-const RESERVED_USERNAMES = new Set(['admin', 'account', 'auth', 'login', 'register', 'logout', 'user', 'users', 'api', 'fediverse', 'posts', 'media', 'audio', 'assets', 'cirkel', 'authorize_interaction']);
-
 router.post('/profile', requireAuth, (req, res) => {
   const bio = (req.body.bio || '').toString().slice(0, 500).trim();
-
-  // Username (login + display name; does NOT affect the fediverse handle, which
-  // is the site slug). Validate: format + reserved + unique (case-insensitive).
-  const username = (req.body.username || '').toString().trim();
-  if (username && username !== req.session.user.username) {
-    if (!/^[A-Za-z0-9_-]{2,30}$/.test(username)) {
-      return res.redirect('/account?error=' + encodeURIComponent('Gebruikersnaam: 2-30 tekens; letters, cijfers, _ en - .'));
-    }
-    if (RESERVED_USERNAMES.has(username.toLowerCase())) {
-      return res.redirect('/account?error=' + encodeURIComponent('Die gebruikersnaam is gereserveerd.'));
-    }
-    const uTaken = db.prepare('SELECT 1 FROM users WHERE LOWER(username) = LOWER(?) AND id != ?').get(username, req.session.user.id);
-    if (uTaken) {
-      return res.redirect('/account?error=' + encodeURIComponent('Die gebruikersnaam is al in gebruik.'));
-    }
-    db.prepare('UPDATE users SET username = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(username, req.session.user.id);
-    req.session.user.username = username;
-  }
-
-  // Email (optionally also changed). Validation: valid format + not already in use
-  // by another account. Email is the login/reset anchor, so it must be unique.
-  const email = (req.body.email || '').toString().trim();
-  if (email) {
-    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || email.length > 254) {
-      return res.redirect('/account?error=' + encodeURIComponent('Voer een geldig e-mailadres in.'));
-    }
-    const taken = db.prepare('SELECT 1 FROM users WHERE LOWER(email) = LOWER(?) AND id != ?')
-      .get(email, req.session.user.id);
-    if (taken) {
-      return res.redirect('/account?error=' + encodeURIComponent('Dit e-mailadres is al in gebruik.'));
-    }
-    db.prepare('UPDATE users SET email = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
-      .run(email, req.session.user.id);
-    req.session.user.email = email; // update session so the UI reflects the change
-  }
-
   db.prepare('UPDATE users SET bio = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
     .run(bio || null, req.session.user.id);
-  res.redirect('/account?success=' + encodeURIComponent('Profiel bijgewerkt'));
+  res.redirect('/account?success=' + encodeURIComponent('Profile updated'));
 });
 
@@ -184,20 +88,16 @@
   const { current, new_password, confirm } = req.body;
   if (!current || !new_password || !confirm) {
-    return res.redirect('/account?error=' + encodeURIComponent('Alle wachtwoordvelden zijn verplicht'));
+    return res.redirect('/account?error=' + encodeURIComponent('All password fields required'));
   }
   if (new_password.length < 8) {
-    return res.redirect('/account?error=' + encodeURIComponent('Nieuw wachtwoord moet minstens 8 tekens zijn'));
+    return res.redirect('/account?error=' + encodeURIComponent('New password must be at least 8 characters'));
   }
   if (new_password !== confirm) {
-    return res.redirect('/account?error=' + encodeURIComponent('Nieuwe wachtwoorden komen niet overeen'));
+    return res.redirect('/account?error=' + encodeURIComponent('New passwords do not match'));
   }
 
   const row = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.session.user.id);
-  // Google-only accounts (listeners) have no real password.
-  if (!row || !row.password_hash || row.password_hash === '!google-oauth') {
-    return res.redirect('/account?error=' + encodeURIComponent('Dit account heeft geen wachtwoord (Google-login)'));
-  }
-  if (!bcrypt.compareSync(current, row.password_hash)) {
-    return res.redirect('/account?error=' + encodeURIComponent('Huidig wachtwoord is onjuist'));
+  if (!row || !bcrypt.compareSync(current, row.password_hash)) {
+    return res.redirect('/account?error=' + encodeURIComponent('Current password is incorrect'));
   }
 
@@ -206,5 +106,5 @@
     .run(newHash, req.session.user.id);
 
-  res.redirect('/account?success=' + encodeURIComponent('Wachtwoord gewijzigd'));
+  res.redirect('/account?success=' + encodeURIComponent('Password changed'));
 });
 
@@ -219,5 +119,5 @@
     }
 
-    const url = `/media/avatars/${toWebp(req.file)}`;
+    const url = `/media/avatars/${req.file.filename}`;
 
     // Remove the old avatar file (if it lives in our avatar dir)
