refactor(scripts): delete 20 one-off/legacy scripts
The scripts/ directory had accumulated 29 entries, half of which were one-off fixes that had already done their job and several of which were Supabase-era artifacts that don't apply now that the project uses direct Postgres via `pg`. Deleted (none referenced from package.json, CLAUDE.md, MEMORY.md, .gitea/workflows/deploy.yml, db/seeds/, or src/): Supabase-era one-offs (the project moved off Supabase JS/REST): - apply-admin-create-stop.js (RPC installer against supabase.co) - fix-archived-rls.js (RLS repair against supabase.co) - seed.sh (pure supabase REST seeding) - seed_tuxedo_tour.py (Python equivalent of the JS seed) Codemods / lint fixers that ran once: - fix-server-auth.js, fix-server-auth-ast.js - fix-button-has-type.js, fix-control-has-associated-label.js Versioned iteration history of one-off checks: - check-stop-fns.js, check-stop-fns2.js, check-stop-fns3.js - verify-stop-fns.js (only referenced by apply-admin-create-stop.js) Other one-offs: - cleanup-duplicate-stops.ts - create-admin-user.ts, seed-admin.ts (superseded by provision-admin.ts) - seed-tuxedo.ts (superseded by seed-tuxedo-2026.js) - import-woo-to-route.ts (WooCommerce import, no longer used) - upload-tuxedo-video.mjs, verify-email.ts, e2e-test.sh Also updated one stale comment in src/actions/admin/users.ts that referenced scripts/seed-admin.ts. Kept (9 scripts, all live-referenced): - migrate.js (package.json + deploy.yml + MEMORY.md) - db-reset.js (package.json: db:reset) - seed-tuxedo-2026.js (package.json: db:seed:tour) - import-tuxedo-stops.ts (db/seeds preferred path) - provision-admin.ts (CLAUDE.md production bootstrap) - preflight-check.js, postflight-check.js (.gitea/workflows/deploy.yml) - generate-pwa-icons.js, generate-pwa-screenshots.js (reproducible PWA build assets, referenced in design docs) Verified: tsc clean, 174/175 tests pass (same baseline), package.json scripts and deploy.yml all still resolve to kept scripts.
This commit is contained in:
@@ -1,228 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* fix-archived-rls.js
|
||||
*
|
||||
* Add proper RLS enables + policies to tables created in
|
||||
* supabase/migrations/.archived/*.sql files. These files are
|
||||
* historical/archived migrations (per commit 916ad39 "Supabase removal")
|
||||
* but react-doctor scans them. We satisfy the rule with real, correct
|
||||
* policies scoped by user_id (auth.uid()) or brand_id (current_brand_id())
|
||||
* where possible; deny-all otherwise.
|
||||
*
|
||||
* Tables are detected by parsing CREATE TABLE blocks. The list of
|
||||
* target files comes from the react-doctor scan output:
|
||||
* - supabase-table-missing-rls (22 errors)
|
||||
* - supabase-rls-policy-risk (9 errors in archived files; 1 in init.sql fixed separately)
|
||||
*
|
||||
* Idempotent: skips tables that already have RLS enabled.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const ARCHIVE_DIR = path.join(__dirname, "..", "supabase", "migrations", ".archived");
|
||||
|
||||
// Helper policy templates.
|
||||
// `brand_id`-scoped (most common in archived files):
|
||||
// USING (brand_id = current_brand_id() OR is_platform_admin())
|
||||
// WITH CHECK (brand_id = current_brand_id() OR is_platform_admin())
|
||||
// `user_id`-scoped:
|
||||
// USING (user_id = auth.uid() OR is_platform_admin())
|
||||
// WITH CHECK (user_id = auth.uid() OR is_platform_admin())
|
||||
// Public-read (e.g. blog_posts):
|
||||
// SELECT to anon/authenticated USING (true)
|
||||
// INSERT/UPDATE/DELETE: USING (false) WITH CHECK (false)
|
||||
|
||||
function policyFor(tableName, columns) {
|
||||
// Heuristics based on columns present:
|
||||
const hasBrandId = columns.some(c => /^brand_id\b/i.test(c));
|
||||
const hasUserId = columns.some(c => /^user_id\b/i.test(c) && !/"userId"/i.test(c));
|
||||
const hasUserIdCamel = columns.some(c => /"userId"/i.test(c));
|
||||
const hasContactEmail = columns.some(c => /^contact_email\b/i.test(c));
|
||||
const hasEmail = columns.some(c => /^email\b/i.test(c) && !/email_sent/i.test(c));
|
||||
|
||||
// Public-read-only with deny write: blog_posts / blog_resources / roadmap_items (public-facing)
|
||||
const publicReadOnly = new Set([
|
||||
"blog_posts", "blog_resources", "roadmap_items",
|
||||
"newsletter_subscribers", "waitlist",
|
||||
]);
|
||||
if (publicReadOnly.has(tableName)) {
|
||||
return [
|
||||
`-- RLS: ${tableName} is public-read; deny writes by default`,
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS public_read ON ${tableName};`,
|
||||
`CREATE POLICY public_read ON ${tableName} FOR SELECT USING (true);`,
|
||||
`DROP POLICY IF EXISTS deny_write ON ${tableName};`,
|
||||
`CREATE POLICY deny_write ON ${tableName} FOR INSERT WITH CHECK (false);`,
|
||||
`DROP POLICY IF EXISTS deny_update ON ${tableName};`,
|
||||
`CREATE POLICY deny_update ON ${tableName} FOR UPDATE USING (false) WITH CHECK (false);`,
|
||||
`DROP POLICY IF EXISTS deny_delete ON ${tableName};`,
|
||||
`CREATE POLICY deny_delete ON ${tableName} FOR DELETE USING (false);`,
|
||||
];
|
||||
}
|
||||
|
||||
// verification_token (Auth.js): no RLS needed since it's used as the
|
||||
// session lookup, but rule requires it. Lock to service role only.
|
||||
if (tableName === "verification_token") {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS deny_all ON ${tableName};`,
|
||||
`CREATE POLICY deny_all ON ${tableName} FOR ALL USING (false) WITH CHECK (false);`,
|
||||
];
|
||||
}
|
||||
|
||||
// users / accounts / sessions: Auth.js tables, scoped by userId.
|
||||
if (hasUserIdCamel) {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS self_access ON ${tableName};`,
|
||||
`CREATE POLICY self_access ON ${tableName} FOR ALL USING ("userId" = auth.uid() OR is_platform_admin()) WITH CHECK ("userId" = auth.uid() OR is_platform_admin());`,
|
||||
];
|
||||
}
|
||||
|
||||
// users (Auth.js): self-read own row.
|
||||
if (tableName === "users") {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS self_read ON ${tableName};`,
|
||||
`CREATE POLICY self_read ON ${tableName} FOR SELECT USING (id = auth.uid() OR is_platform_admin());`,
|
||||
`DROP POLICY IF EXISTS deny_write ON ${tableName};`,
|
||||
`CREATE POLICY deny_write ON ${tableName} FOR INSERT WITH CHECK (false);`,
|
||||
`DROP POLICY IF EXISTS deny_update ON ${tableName};`,
|
||||
`CREATE POLICY deny_update ON ${tableName} FOR UPDATE USING (id = auth.uid() OR is_platform_admin()) WITH CHECK (id = auth.uid() OR is_platform_admin());`,
|
||||
`DROP POLICY IF EXISTS deny_delete ON ${tableName};`,
|
||||
`CREATE POLICY deny_delete ON ${tableName} FOR DELETE USING (id = auth.uid() OR is_platform_admin());`,
|
||||
];
|
||||
}
|
||||
|
||||
// sessions: self-access by userId column.
|
||||
if (tableName === "sessions") {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS self_access ON ${tableName};`,
|
||||
`CREATE POLICY self_access ON ${tableName} FOR ALL USING ("userId" = auth.uid() OR is_platform_admin()) WITH CHECK ("userId" = auth.uid() OR is_platform_admin());`,
|
||||
];
|
||||
}
|
||||
|
||||
// launch_checklist_progress: user-scoped.
|
||||
if (tableName === "launch_checklist_progress" && hasUserId) {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS self_access ON ${tableName};`,
|
||||
`CREATE POLICY self_access ON ${tableName} FOR ALL USING (user_id = auth.uid()::text OR is_platform_admin()) WITH CHECK (user_id = auth.uid()::text OR is_platform_admin());`,
|
||||
];
|
||||
}
|
||||
|
||||
// brand-scoped tables: most common.
|
||||
if (hasBrandId) {
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS tenant_isolation ON ${tableName};`,
|
||||
`CREATE POLICY tenant_isolation ON ${tableName} FOR ALL USING (brand_id = current_brand_id() OR is_platform_admin()) WITH CHECK (brand_id = current_brand_id() OR is_platform_admin());`,
|
||||
];
|
||||
}
|
||||
|
||||
// Fallback: deny all (safest default).
|
||||
return [
|
||||
`ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
|
||||
`ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
|
||||
`DROP POLICY IF EXISTS deny_all ON ${tableName};`,
|
||||
`CREATE POLICY deny_all ON ${tableName} FOR ALL USING (false) WITH CHECK (false);`,
|
||||
];
|
||||
}
|
||||
|
||||
// Parse a file and return [{name, columns}] for each CREATE TABLE block.
|
||||
function parseTables(sql) {
|
||||
const tables = [];
|
||||
// Match CREATE TABLE ... (...);
|
||||
const re = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([\s\S]*?)\)\s*;/gi;
|
||||
let match;
|
||||
while ((match = re.exec(sql)) !== null) {
|
||||
const name = match[1];
|
||||
const body = match[2];
|
||||
// Extract column names (first identifier on each comma-separated line)
|
||||
const columns = [];
|
||||
body.split(",").forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return;
|
||||
if (/^(?:CONSTRAINT|UNIQUE|PRIMARY\s+KEY|FOREIGN\s+KEY|CHECK|INDEX)\b/i.test(trimmed)) return;
|
||||
const colMatch = trimmed.match(/^(?:"[^"]+"|`[^`]+`|[a-zA-Z_][a-zA-Z0-9_]*)/);
|
||||
if (colMatch) columns.push(colMatch[0]);
|
||||
});
|
||||
tables.push({ name, columns });
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
const files = [
|
||||
"005_water_log.sql",
|
||||
"044_square_sync_log.sql",
|
||||
"066_auto_square_sync.sql",
|
||||
"069_brand_scoping_phase2.sql",
|
||||
"070_rls_policy_audit.sql",
|
||||
"080_communication_segments.sql",
|
||||
"088_brand_features.sql",
|
||||
"120_water_alerts.sql",
|
||||
"126_founder_pain_log.sql",
|
||||
"129_worker_time_tracking.sql",
|
||||
"130_time_tracking_pay_period_overtime.sql",
|
||||
"133_time_tracking_notifications.sql",
|
||||
"134_abandoned_cart_recovery.sql",
|
||||
"137_time_tracking_workers_and_tasks.sql",
|
||||
"138_time_tracking_logs.sql",
|
||||
"204_authjs_tables.sql",
|
||||
"XXX_blog_tables.sql",
|
||||
"XXX_launch_checklist.sql",
|
||||
"XXX_roadmap_tables.sql",
|
||||
"XXX_waitlist_table.sql",
|
||||
];
|
||||
|
||||
let totalAdded = 0;
|
||||
let totalSkipped = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(ARCHIVE_DIR, file);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log(`skip (not found): ${file}`);
|
||||
continue;
|
||||
}
|
||||
const original = fs.readFileSync(filePath, "utf8");
|
||||
const tables = parseTables(original);
|
||||
if (tables.length === 0) {
|
||||
console.log(`skip (no tables): ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Idempotency check: skip if "ENABLE ROW LEVEL SECURITY" already present for any of the tables.
|
||||
const newTables = tables.filter(t => !new RegExp(`ALTER\\s+TABLE\\s+${t.name}\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`, "i").test(original));
|
||||
if (newTables.length === 0) {
|
||||
console.log(`skip (already done): ${file} — ${tables.length} tables`);
|
||||
totalSkipped += tables.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const blocks = newTables.map(t => {
|
||||
const policy = policyFor(t.name, t.columns);
|
||||
return [
|
||||
`-- ============================================================`,
|
||||
`-- RLS for ${t.name} (added by scripts/fix-archived-rls.js)`,
|
||||
`-- Columns: ${t.columns.join(", ")}`,
|
||||
`-- ============================================================`,
|
||||
...policy,
|
||||
].join("\n");
|
||||
});
|
||||
|
||||
const appended = blocks.join("\n\n") + "\n";
|
||||
fs.writeFileSync(filePath, original + "\n" + appended);
|
||||
console.log(`patched: ${file} — added RLS for ${newTables.length} table(s): ${newTables.map(t => t.name).join(", ")}`);
|
||||
totalAdded += newTables.length;
|
||||
}
|
||||
|
||||
console.log(`\nTotal: added ${totalAdded} RLS block(s), skipped ${totalSkipped} already-done.`);
|
||||
Reference in New Issue
Block a user