aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-12-08 11:02:59 +0000
committerMohamed Bassem <me@mbassem.com>2025-12-08 11:02:59 +0000
commit69a756aadd94c7a3f648373e1315edb8a245f20d (patch)
tree3bc26cb1009d356d525abc7dc505ad90cf671dc4
parent6886385ce239f63271639f047093ba307c87f2e6 (diff)
downloadkarakeep-69a756aadd94c7a3f648373e1315edb8a245f20d.tar.zst
feat(cli): Add ability to list users for the admin in the CLImain
-rw-r--r--apps/cli/src/commands/admin.ts89
-rw-r--r--apps/cli/src/index.ts2
2 files changed, 91 insertions, 0 deletions
diff --git a/apps/cli/src/commands/admin.ts b/apps/cli/src/commands/admin.ts
new file mode 100644
index 00000000..181126f0
--- /dev/null
+++ b/apps/cli/src/commands/admin.ts
@@ -0,0 +1,89 @@
+import { getGlobalOptions } from "@/lib/globals";
+import { printErrorMessageWithReason, printObject } from "@/lib/output";
+import { getAPIClient } from "@/lib/trpc";
+import { Command } from "@commander-js/extra-typings";
+import { getBorderCharacters, table } from "table";
+
+export const adminCmd = new Command()
+ .name("admin")
+ .description("admin commands");
+
+function toHumanReadableSize(size: number): string {
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
+ if (size === 0) return "0 Bytes";
+ const i = Math.floor(Math.log(size) / Math.log(1024));
+ return (size / Math.pow(1024, i)).toFixed(2) + " " + sizes[i];
+}
+
+const usersCmd = new Command()
+ .name("users")
+ .description("user management commands");
+
+usersCmd
+ .command("list")
+ .description("list all users")
+ .action(async () => {
+ const api = getAPIClient();
+
+ try {
+ const [usersResp, userStats] = await Promise.all([
+ api.users.list.query(),
+ api.admin.userStats.query(),
+ ]);
+
+ if (getGlobalOptions().json) {
+ printObject({
+ users: usersResp.users.map((u) => ({
+ ...u,
+ numBookmarks: userStats[u.id]?.numBookmarks ?? 0,
+ assetSizes: userStats[u.id]?.assetSizes ?? 0,
+ })),
+ });
+ } else {
+ const data: string[][] = [
+ [
+ "Name",
+ "Email",
+ "Num Bookmarks",
+ "Asset Sizes",
+ "Role",
+ "Local User",
+ ],
+ ];
+
+ usersResp.users.forEach((user) => {
+ const stats = userStats[user.id] ?? {
+ numBookmarks: 0,
+ assetSizes: 0,
+ };
+
+ const numBookmarksDisplay = `${stats.numBookmarks} / ${user.bookmarkQuota?.toString() ?? "Unlimited"}`;
+ const assetSizesDisplay = `${toHumanReadableSize(stats.assetSizes)} / ${user.storageQuota ? toHumanReadableSize(user.storageQuota) : "Unlimited"}`;
+
+ data.push([
+ user.name,
+ user.email,
+ numBookmarksDisplay,
+ assetSizesDisplay,
+ user.role ?? "",
+ user.localUser ? "✓" : "✗",
+ ]);
+ });
+
+ console.log(
+ table(data, {
+ border: getBorderCharacters("ramac"),
+ drawHorizontalLine: (lineIndex, rowCount) => {
+ return (
+ lineIndex === 0 || lineIndex === 1 || lineIndex === rowCount
+ );
+ },
+ }),
+ );
+ }
+ } catch (error) {
+ printErrorMessageWithReason("Failed to list all users", error as object);
+ }
+ });
+
+adminCmd.addCommand(usersCmd);
diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts
index df7d9512..8158c0b8 100644
--- a/apps/cli/src/index.ts
+++ b/apps/cli/src/index.ts
@@ -1,3 +1,4 @@
+import { adminCmd } from "@/commands/admin";
import { bookmarkCmd } from "@/commands/bookmarks";
import { dumpCmd } from "@/commands/dump";
import { listsCmd } from "@/commands/lists";
@@ -31,6 +32,7 @@ const program = new Command()
: "0.0.0",
);
+program.addCommand(adminCmd);
program.addCommand(bookmarkCmd);
program.addCommand(listsCmd);
program.addCommand(tagsCmd);