Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
72
scripts/create-admin.ts
Normal file
72
scripts/create-admin.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fs from "fs";
|
||||
|
||||
// Load .env.local into process.env (same pattern as other scripts) BEFORE the
|
||||
// db module initialises, so it picks up the real DATABASE_URL (port 5436).
|
||||
const dotenvContent = fs.readFileSync(".env.local", "utf8");
|
||||
for (const l of dotenvContent.split("\n")) {
|
||||
const trimmed = l.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const idx = trimmed.indexOf("=");
|
||||
if (idx !== -1) {
|
||||
process.env[trimmed.slice(0, idx).trim()] = trimmed.slice(idx + 1).trim();
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { db } = await import("../src/lib/db");
|
||||
const { users } = await import("../src/lib/schema/db");
|
||||
const { hashPassword } = await import("../src/lib/auth/password");
|
||||
const { canonicaliseEmail, normaliseEmail } = await import("../src/lib/auth/email");
|
||||
const { newId } = await import("../src/lib/auth/tokens");
|
||||
const { eq } = await import("drizzle-orm");
|
||||
|
||||
const rawEmail = process.env.ADMIN_EMAILS?.split(",")[0]?.trim() ?? "";
|
||||
const password = process.env.ADMIN_PASSWORD ?? "fiesta";
|
||||
|
||||
if (!rawEmail) throw new Error("ADMIN_EMAILS not set");
|
||||
|
||||
const email = canonicaliseEmail(rawEmail);
|
||||
const emailKey = normaliseEmail(rawEmail);
|
||||
if (!emailKey) throw new Error(`Invalid admin email: ${rawEmail}`);
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
const existing = await db.select().from(users).where(eq(users.emailKey, emailKey)).limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(users)
|
||||
.set({
|
||||
passwordHash,
|
||||
emailVerifiedAt: new Date(),
|
||||
isGuest: false,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(users.id, existing[0].id));
|
||||
console.log(`Updated admin ${email} (${existing[0].id}) — password reset, email verified`);
|
||||
} else {
|
||||
const row = (
|
||||
await db
|
||||
.insert(users)
|
||||
.values({
|
||||
id: newId("usr"),
|
||||
email,
|
||||
emailKey,
|
||||
name: "Timo Knuth",
|
||||
passwordHash,
|
||||
emailVerifiedAt: new Date(),
|
||||
isGuest: false,
|
||||
plan: "free",
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
console.log(`Created admin ${email} (${row.id}) — email verified`);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user