/** * Prepares the chat's `@everyone`: it adds `chat_messages.mentions_everyone`, * and frees the name `everyone`, which no account may have any more. * * @module */ import type { DatabaseSync } from "node:sqlite"; import { EVERYONE, mentionsEveryone } from "../../../shared/mention.ts"; import { ROLE_PERMISSIONS } from "../../../shared/permissions.ts"; /** * Run once by `runMigrations`. The messages that a moderator or an admin * already sent with `@everyone` get the flag, so that editing one later * doesn't notify everyone of an old message; none is notified now. A user * named `everyone`, in any case, is renamed to the first free `everyone_2`, * `everyone_3`…, with no alias left behind, and an alias `everyone` is * dropped. Both are logged. */ export function up(db: DatabaseSync): void { const cols = db.prepare(`PRAGMA table_info(chat_messages);`).all() as { name: string; }[]; if (!cols.some((c) => c.name === "mentions_everyone")) { db.exec( `ALTER TABLE chat_messages ADD COLUMN mentions_everyone INTEGER NOT NULL DEFAULT 0;`, ); } const roles = Object.entries(ROLE_PERMISSIONS) .filter(([, perms]) => perms.includes("chat:everyone")) .map(([role]) => role); const messages = db.prepare( `SELECT m.id, m.body FROM chat_messages m JOIN users u ON u.id = m.user_id WHERE m.mentions_everyone = 0 AND u.role IN (${roles.map(() => "?").join(", ")}) AND m.body LIKE '%@%';`, ).all(...roles) as { id: string; body: string }[]; const flag = db.prepare( `UPDATE chat_messages SET mentions_everyone = 1 WHERE id = ?;`, ); for (const m of messages) { if (mentionsEveryone(m.body)) flag.run(m.id); } const taken = (name: string) => !!db.prepare( `SELECT 1 FROM users WHERE lower(username) = lower(?) UNION ALL SELECT 1 FROM user_aliases WHERE lower(username) = lower(?);`, ).get(name, name); const user = db.prepare( `SELECT id, username FROM users WHERE lower(username) = ?;`, ).get(EVERYONE) as { id: string; username: string } | undefined; if (user) { let n = 2; while (taken(`${EVERYONE}_${n}`)) n++; const renamed = `${EVERYONE}_${n}`; db.prepare(`UPDATE users SET username = ? WHERE id = ?;`).run( renamed, user.id, ); console.log( `[migrate] user "${user.username}" is now "${renamed}": @${EVERYONE} is reserved`, ); } const alias = db.prepare( `DELETE FROM user_aliases WHERE lower(username) = ? RETURNING user_id;`, ).get(EVERYONE) as { user_id: string } | undefined; if (alias) { console.log( `[migrate] the former name "${EVERYONE}" of user ${alias.user_id} is dropped: @${EVERYONE} is reserved`, ); } }