// src/web-cli.ts // Copyright (C) 2026 Robert Colbert // 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { await this.startServices(); } async startServices(): Promise { 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); } })();