7a2ae8434d
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true
133 lines
3.5 KiB
JavaScript
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);
|
|
});
|