initial project setup
Fastify + Prisma backend, React + Vite frontend, Docker deployment. Multi-board feedback platform with anonymous cookie auth, passkey upgrade path, ALTCHA spam protection, plugin system, and full privacy-first architecture.
This commit is contained in:
86
packages/api/src/cli/create-admin.ts
Normal file
86
packages/api/src/cli/create-admin.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import bcrypt from "bcrypt";
|
||||
import { createInterface } from "node:readline";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function getArg(name: string): string | undefined {
|
||||
const idx = process.argv.indexOf(`--${name}`);
|
||||
if (idx === -1 || idx + 1 >= process.argv.length) return undefined;
|
||||
return process.argv[idx + 1];
|
||||
}
|
||||
|
||||
function readLine(prompt: string, hidden = false): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
if (hidden) {
|
||||
process.stdout.write(prompt);
|
||||
let input = "";
|
||||
process.stdin.setRawMode?.(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding("utf8");
|
||||
const handler = (ch: string) => {
|
||||
if (ch === "\n" || ch === "\r" || ch === "\u0004") {
|
||||
process.stdin.setRawMode?.(false);
|
||||
process.stdin.removeListener("data", handler);
|
||||
process.stdout.write("\n");
|
||||
rl.close();
|
||||
resolve(input);
|
||||
} else if (ch === "\u007F" || ch === "\b") {
|
||||
if (input.length > 0) {
|
||||
input = input.slice(0, -1);
|
||||
process.stdout.write("\b \b");
|
||||
}
|
||||
} else {
|
||||
input += ch;
|
||||
process.stdout.write("*");
|
||||
}
|
||||
};
|
||||
process.stdin.on("data", handler);
|
||||
} else {
|
||||
rl.question(prompt, (answer) => {
|
||||
rl.close();
|
||||
resolve(answer);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const email = getArg("email") ?? await readLine("Email: ");
|
||||
if (!email) {
|
||||
console.error("Email is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const existing = await prisma.adminUser.findUnique({ where: { email } });
|
||||
if (existing) {
|
||||
console.error("Admin with this email already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const password = await readLine("Password: ", true);
|
||||
if (!password || password.length < 8) {
|
||||
console.error("Password must be at least 8 characters");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const confirm = await readLine("Confirm password: ", true);
|
||||
if (password !== confirm) {
|
||||
console.error("Passwords do not match");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hash = await bcrypt.hash(password, 12);
|
||||
const admin = await prisma.adminUser.create({
|
||||
data: { email, passwordHash: hash },
|
||||
});
|
||||
|
||||
console.log(`Admin created: ${admin.email} (${admin.id})`);
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user