Files
TX/artifacts/api-server/scripts/gate-executive-meetings.mjs
Riyadh 0cee551f60 Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers.
2026-05-14 06:23:49 +00:00

133 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
/**
* One-shot, idempotent migration for.
*
* Adds the `executive_meetings.access` permission, grants it to admin + all
* executive_* roles, and links it to the `executive-meetings` app via
* `app_permissions` so the home-screen icon is hidden from users who don't
* have an executive role.
*
* Safe to run multiple times — every insert uses ON CONFLICT DO NOTHING.
*
* Usage (from repo root):
* DATABASE_URL=... node artifacts/api-server/scripts/gate-executive-meetings.mjs
*/
import pg from "pg";
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
console.error("DATABASE_URL must be set");
process.exit(1);
}
const pool = new pg.Pool({ connectionString: DATABASE_URL });
const EXEC_ROLE_NAMES = [
"admin",
"executive_ceo",
"executive_office_manager",
"executive_coord_lead",
"executive_coordinator",
"executive_viewer",
];
async function main() {
const client = await pool.connect();
try {
await client.query("BEGIN");
// 1) Permission row.
await client.query(
`INSERT INTO permissions (name, description_ar, description_en)
VALUES ($1, $2, $3)
ON CONFLICT (name) DO NOTHING`,
[
"executive_meetings.access",
"الوصول إلى وحدة الاجتماعات التنفيذية",
"Access the Executive Meetings module",
],
);
const permRes = await client.query(
`SELECT id FROM permissions WHERE name = $1`,
["executive_meetings.access"],
);
if (permRes.rowCount === 0) {
throw new Error("Failed to find executive_meetings.access permission");
}
const permId = permRes.rows[0].id;
// 2) Role grants.
const grantRes = await client.query(
`INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, $1 FROM roles r WHERE r.name = ANY($2::text[])
ON CONFLICT DO NOTHING
RETURNING role_id`,
[permId, EXEC_ROLE_NAMES],
);
// 3) App link.
const appRes = await client.query(
`SELECT id FROM apps WHERE slug = $1`,
["executive-meetings"],
);
if (appRes.rowCount === 0) {
throw new Error(
"executive-meetings app not found in the apps table; aborting",
);
}
const appId = appRes.rows[0].id;
const appLinkRes = await client.query(
`INSERT INTO app_permissions (app_id, permission_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
RETURNING app_id`,
[appId, permId],
);
await client.query("COMMIT");
// Final state for the operator.
const totalGrants = await client.query(
`SELECT COUNT(*)::int AS c
FROM role_permissions rp
JOIN roles r ON r.id = rp.role_id
WHERE rp.permission_id = $1 AND r.name = ANY($2::text[])`,
[permId, EXEC_ROLE_NAMES],
);
const totalLinks = await client.query(
`SELECT COUNT(*)::int AS c
FROM app_permissions
WHERE permission_id = $1`,
[permId],
);
console.log(
JSON.stringify(
{
ok: true,
permissionId: permId,
executiveAppId: appId,
newRoleGrantsThisRun: grantRes.rowCount ?? 0,
newAppLinksThisRun: appLinkRes.rowCount ?? 0,
executiveRoleGrantsTotal: totalGrants.rows[0].c,
appPermissionLinksTotal: totalLinks.rows[0].c,
},
null,
2,
),
);
} catch (err) {
await client.query("ROLLBACK").catch(() => {});
throw err;
} finally {
client.release();
await pool.end();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});