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:
2026-03-19 18:05:16 +02:00
commit f07eddf29e
77 changed files with 7031 additions and 0 deletions

View 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);
});