gadget/gadget-code/src/web-cli.ts
2026-04-28 09:20:37 -04:00

342 lines
8.4 KiB
TypeScript

// src/web-cli.ts
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
// All Rights Reserved
import { v4 as uuidv4 } from "uuid";
import { Types } from "mongoose";
import "./lib/db.js";
import ApiClient, { ApiClientStatus } from "./models/api-client.js";
import User from "./models/user.js";
import ApiClientService from "./services/api-client.js";
import CryptoService from "./services/crypto.js";
import UserService from "./services/user.js";
import { DtpProcess } from "./lib/process.js";
class DtpWebCli extends DtpProcess {
get name(): string {
return "DtpWebCli";
}
get slug(): string {
return "dtp-web-cli";
}
constructor() {
super();
}
async run(argv: string[]): Promise<void> {
const cmd = argv.shift();
if (!cmd) {
throw new Error("must specify command");
}
switch (cmd) {
case "admin":
return this.onAdminCmd(argv);
case "api-client":
return this.onApiClientCmd(argv);
case "user":
return this.onUserCmd(argv);
default:
break;
}
throw new Error(`unknown command: ${cmd}`);
}
async onAdminCmd(argv: string[]): Promise<void> {
const action = argv.shift();
if (!action) {
throw new Error("must specify user command action");
}
switch (action) {
case "grant":
return this.onAdminGrant(argv);
case "revoke":
return this.onAdminRevoke(argv);
default:
break;
}
throw new Error(`unknown user command action: ${action}`);
}
async onAdminGrant(argv: string[]): Promise<void> {
const email = argv.shift();
if (!email) {
throw new Error("must specify email for admin grant");
}
const user = await User.findOne({
email_lc: email.trim().toLowerCase(),
});
if (!user) {
throw new Error(`user ${email} not found`);
}
user.flags.isAdmin = true;
await user.save();
this.log.info(`admin rights granted to ${email}`);
}
async onAdminRevoke(argv: string[]): Promise<void> {
const email = argv.shift();
if (!email) {
throw new Error("must specify email for admin revoke");
}
const user = await User.findOne({
email_lc: email.trim().toLowerCase(),
});
if (!user) {
throw new Error(`user ${email} not found`);
}
user.flags.isAdmin = false;
await user.save();
this.log.info(`admin rights revoked from ${email}`);
}
async onApiClientCmd(argv: string[]): Promise<void> {
const action = argv.shift();
if (!action) {
throw new Error("must specify api client command action");
}
switch (action) {
case "add":
return this.onApiClientAdd(argv);
case "ls":
return this.onApiClientList(argv);
case "status":
return this.onApiClientSetStatus(argv);
case "remove":
return this.onApiClientRemove(argv);
default:
break;
}
throw new Error(`unknown api client command action: ${action}`);
}
async onApiClientAdd(argv: string[]): Promise<void> {
const name = argv.shift();
const description = argv.shift();
const client = await ApiClientService.create({
name,
description,
});
this.log.info("api client added", {
client: {
_id: client._id,
secret: client.secret,
name: client.name,
},
});
}
async onApiClientList(_argv: string[]): Promise<void> {
const clients = await ApiClient.find({ status: ApiClientStatus.Active })
.sort({ name: 1 })
.lean();
console.log("Name".padEnd(20), "Client ID".padEnd(24), "Secret");
console.log(
"--------------------------------------------------------------------------------"
);
for (const client of clients) {
console.log(client.name.padEnd(20), client._id.toString(), client.secret);
}
}
async onApiClientSetStatus(argv: string[]): Promise<void> {
const clientId = argv.shift();
if (!clientId) {
throw new Error("client ID is required");
}
const clientIdObj = Types.ObjectId.createFromHexString(clientId);
const client = await ApiClientService.getById(clientIdObj);
if (!client) {
throw new Error("Client not found");
}
const status = argv.shift() as ApiClientStatus;
if (!status) {
throw new Error("New client status is required");
}
await ApiClientService.setStatus(client, status);
this.log.info("API client status updated", { _id: clientId, status });
}
async onApiClientRemove(argv: string[]): Promise<void> {
const clientId = argv.shift();
if (!clientId) {
throw new Error("client ID is required");
}
const clientIdObj = Types.ObjectId.createFromHexString(clientId);
const client = await ApiClientService.getById(clientIdObj);
if (!client) {
throw new Error("Client not found");
}
await ApiClientService.remove(client);
this.log.info("API client removed", { _id: clientId });
}
async onUserCmd(argv: string[]): Promise<void> {
const action = argv.shift();
if (!action) {
throw new Error("must specify user command action");
}
switch (action) {
case "add":
return this.onUserAdd(argv);
case "password":
return this.onUserPassword(argv);
case "remove":
return this.onUserRemove(argv);
case "ban":
return this.onUserBan(argv);
default:
break;
}
throw new Error(`unknown user command action: ${action}`);
}
async onUserAdd(argv: string[]): Promise<void> {
const email = argv.shift();
if (!email) {
throw new Error("must specify email address");
}
const password = argv.shift();
if (!password) {
throw new Error("must specify password");
}
const displayName: string | undefined = argv.shift();
const user = await UserService.create(email, password, displayName);
this.log.info(
`user created: id:${user._id.toHexString()}, email:${user.email}`
);
}
async onUserRemove(argv: string[]): Promise<void> {
let email = argv.shift();
if (!email) {
throw new Error("must specify email address");
}
email = email.trim().toLowerCase();
const user = await User.findOne({ email }).lean();
if (!user) {
throw new Error(`user not found: ${email}`);
}
await User.deleteOne({ _id: user._id });
this.log.info(`user ${email} removed`);
}
async onUserBan(argv: string[]): Promise<void> {
const email = argv.shift();
if (!email) {
throw new Error("must specify email address");
}
const email_lc = email.trim().toLowerCase();
const user = await User.findOne({ email_lc }).lean();
if (!user) {
throw new Error(`user not found: ${email}`);
}
await User.updateOne(
{ _id: user._id },
{
$set: {
"flags.isBanned": true,
},
}
);
this.log.info(`user ${email} banned`);
}
async onUserUnban(argv: string[]): Promise<void> {
const email = argv.shift();
if (!email) {
throw new Error("must specify email address");
}
const email_lc = email.trim().toLowerCase();
const user = await User.findOne({ email_lc }).lean();
if (!user) {
throw new Error(`user not found: ${email}`);
}
await User.updateOne(
{ _id: user._id },
{
$set: {
"flags.isBanned": false,
},
}
);
this.log.info(`user ${email} unbanned`);
}
async onUserPassword(argv: string[]): Promise<void> {
let email = argv.shift();
if (!email) {
throw new Error("must specify email address");
}
const password = argv.shift();
if (!password) {
throw new Error("must specify password");
}
const email_lc = email.trim().toLowerCase();
const user = await User.findOne({ email_lc });
if (!user) {
throw new Error(`user not found: ${email}`);
}
user.passwordSalt = uuidv4();
user.password = await CryptoService.maskPassword(
user.passwordSalt,
password
);
await user.save();
this.log.info(`user ${email} password changed`);
}
async start(): Promise<void> {
await this.startServices();
}
async startServices(): Promise<void> {
await ApiClientService.start();
}
}
(async () => {
try {
const cli = new DtpWebCli();
await cli.start();
await cli.run(process.argv.slice(2));
process.exit(0);
} catch (error) {
console.error((error as Error).message);
process.exit(-1);
}
})();