Files
Felitendo ff0d3108fe feat: merge the web dashboard into the monorepo as ftstore admin
FTStore now holds both halves of the product:

  ios/   the swift app — the fork of claration/Feather, unchanged apart
         from its new home
  web/   FTStore Admin, formerly its own repository. serves the
         dashboard at /admin, the ftstore.json feed and IPA downloads

app-repo.json and Images/ stay at the repo root on purpose: the former is
a published AltStore source whose raw.githubusercontent URL is what
installed copies resolve updates through, so moving it would break every
subscriber.

The admin server is renamed throughout: package ftstore-admin, images
ghcr.io/<owner>/ftstore-admin-{app,worker}, postgres role/db ftstore,
session cookie ftstore_session, temp dir /tmp/ftstore, backup type
ftstore-settings, outbound user-agent FTStoreAdmin. backups written under the old
name still import, and compose is pinned to name: ftstore so the
volume prefix does not follow the directory name.

apply-rebrand.sh gains a relocation step: upstream's tree is rooted at
the repo root while ours lives in ios/, so a merge drops Feather's files
next to ios/ and the script folds them in before renaming. it never
touches web/. still idempotent.

release tags are namespaced — v* for the app, web-v* for the server —
so a server release no longer triggers a macOS build or rewrites
app-repo.json. workflows are prefixed ios-/web- and path-filtered.
2026-08-03 10:25:03 +02:00

130 lines
3.6 KiB
TypeScript

import { PrismaClient } from "@prisma/client";
import { randomBytes, scryptSync } from "crypto";
const prisma = new PrismaClient();
function hashPassword(password: string): string {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(password, salt, 64).toString("hex");
return `${salt}:${hash}`;
}
async function main() {
const [, , command, ...args] = process.argv;
switch (command) {
case "create": {
const [username, password] = args;
const isAdmin = args.includes("--admin");
if (!username || !password) {
console.error("Usage: manage-users create <username> <password> [--admin]");
process.exit(1);
}
const user = await prisma.user.create({
data: {
username,
passwordHash: hashPassword(password),
role: isAdmin ? "admin" : "user",
approved: true,
},
});
console.log(`Created user: ${user.username} (role: ${user.role}, approved: true)`);
break;
}
case "list": {
const users = await prisma.user.findMany({
select: { id: true, username: true, role: true, approved: true, createdAt: true },
orderBy: { createdAt: "asc" },
});
if (users.length === 0) {
console.log("No users found.");
} else {
console.table(users.map((u) => ({
ID: u.id,
Username: u.username,
Role: u.role,
Approved: u.approved ? "Yes" : "No",
Created: u.createdAt.toISOString(),
})));
}
break;
}
case "approve": {
const [username] = args;
if (!username) {
console.error("Usage: manage-users approve <username>");
process.exit(1);
}
await prisma.user.update({
where: { username },
data: { approved: true },
});
console.log(`Approved user: ${username}`);
break;
}
case "set-role": {
const [username, role] = args;
if (!username || !role || !["admin", "user"].includes(role)) {
console.error("Usage: manage-users set-role <username> <admin|user>");
process.exit(1);
}
await prisma.user.update({
where: { username },
data: { role },
});
console.log(`Set role for ${username}: ${role}`);
break;
}
case "reset-password": {
const [username, password] = args;
if (!username || !password) {
console.error("Usage: manage-users reset-password <username> <password>");
process.exit(1);
}
await prisma.user.update({
where: { username },
data: { passwordHash: hashPassword(password) },
});
console.log(`Password reset for: ${username}`);
break;
}
case "delete": {
const [username] = args;
if (!username) {
console.error("Usage: manage-users delete <username>");
process.exit(1);
}
await prisma.user.delete({ where: { username } });
console.log(`Deleted user: ${username}`);
break;
}
default:
console.log(`FTStore Admin User Management CLI
Commands:
create <username> <password> [--admin] Create a new user
list List all users
approve <username> Approve a pending user
set-role <username> <admin|user> Change user role
reset-password <username> <password> Reset user password
delete <username> Delete a user
Docker usage:
docker exec -it <container> npx tsx scripts/manage-users.ts <command>`);
if (command) process.exit(1);
}
await prisma.$disconnect();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});