From 5287ceb99e5a8a73c8fa6be199b1ecdc1e2cd23d Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Mon, 20 Nov 2023 13:58:39 -1000 Subject: [PATCH] Add CLI and CLI tests --- cli.ts | 169 ++++++++++++++++++++++++++++++++++++++ database/entities/User.ts | 2 + index.ts | 27 ++++++ package.json | 3 +- tests/cli.test.ts | 94 +++++++++++++++++++++ 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 cli.ts create mode 100644 tests/cli.test.ts diff --git a/cli.ts b/cli.ts new file mode 100644 index 00000000..b98d3859 --- /dev/null +++ b/cli.ts @@ -0,0 +1,169 @@ +import chalk from "chalk"; +import { client } from "~database/datasource"; +import { createNewLocalUser } from "~database/entities/User"; + +const args = process.argv; + +const help = ` +${chalk.bold(`Usage: bun cli ${chalk.blue("[...flags]")} [...args]`)} + +${chalk.bold("Commands:")} + ${chalk.blue("help")} ${chalk.gray( + "................." + )} Show this help message + ${chalk.blue("user")} ${chalk.gray(".................")} Manage users + ${chalk.blue("create")} ${chalk.gray("...........")} Create a new user + ${chalk.green("username")} ${chalk.gray( + "....." + )} Username of the user + ${chalk.green("password")} ${chalk.gray( + "....." + )} Password of the user + ${chalk.green("email")} ${chalk.gray("........")} Email of the user + ${chalk.yellow("--admin")} ${chalk.gray( + "......" + )} Make the user an admin (optional) + ${chalk.bold("Example:")} ${chalk.bgGray( + `bun cli user create admin password123 admin@gmail.com --admin` + )} + ${chalk.blue("delete")} ${chalk.gray("...........")} Delete a user + ${chalk.green("username")} ${chalk.gray( + "....." + )} Username of the user + ${chalk.bold("Example:")} ${chalk.bgGray( + `bun cli user delete admin` + )} + ${chalk.blue("list")} ${chalk.gray(".............")} List all users + ${chalk.yellow("--admins")} ${chalk.gray( + "....." + )} List only admins (optional) + ${chalk.bold("Example:")} ${chalk.bgGray(`bun cli user list`)} +`; + +if (args.length < 3) { + console.log(help); + process.exit(0); +} + +const command = args[2]; + +switch (command) { + case "help": + console.log(help); + break; + case "user": + switch (args[3]) { + case "create": { + // Check if --admin flag is provided + const argsWithFlags = args.filter(arg => arg.startsWith("--")); + const argsWithoutFlags = args.filter( + arg => !arg.startsWith("--") + ); + + const username = argsWithoutFlags[4]; + const password = argsWithoutFlags[5]; + const email = argsWithoutFlags[6]; + + const admin = argsWithFlags.includes("--admin"); + + // Check if username, password and email are provided + if (!username || !password || !email) { + console.log( + `${chalk.red(`✗`)} Missing username, password or email` + ); + process.exit(1); + } + + // Check if user already exists + const user = await client.user.findFirst({ + where: { + OR: [{ username }, { email }], + }, + }); + + if (user) { + console.log(`${chalk.red(`✗`)} User already exists`); + process.exit(1); + } + + // Create user + const newUser = await createNewLocalUser({ + email: email, + password: password, + username: username, + admin: admin, + }); + + console.log( + `${chalk.green(`✓`)} Created user ${chalk.blue( + newUser.username + )}` + ); + break; + } + case "delete": { + const username = args[4]; + + if (!username) { + console.log(`${chalk.red(`✗`)} Missing username`); + process.exit(1); + } + + const user = await client.user.findFirst({ + where: { + username: username, + }, + }); + + if (!user) { + console.log(`${chalk.red(`✗`)} User not found`); + process.exit(1); + } + + await client.user.delete({ + where: { + id: user.id, + }, + }); + + console.log( + `${chalk.green(`✓`)} Deleted user ${chalk.blue( + user.username + )}` + ); + + break; + } + case "list": { + const admins = args.includes("--admins"); + + const users = await client.user.findMany({ + where: { + isAdmin: admins || undefined, + }, + }); + + console.log( + `${chalk.green(`✓`)} Found ${chalk.blue( + users.length + )} users` + ); + + for (const user of users) { + console.log( + `\t${chalk.blue(user.username)} ${chalk.gray( + user.email + )} ${chalk.green(user.isAdmin ? "Admin" : "User")}` + ); + } + break; + } + default: + console.log(`Unknown command ${chalk.blue(command)}`); + break; + } + break; + default: + console.log(`Unknown command ${chalk.blue(command)}`); + break; +} diff --git a/database/entities/User.ts b/database/entities/User.ts index 694e96ce..23a5d4c0 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -207,6 +207,7 @@ export const createNewLocalUser = async (data: { bio?: string; avatar?: string; header?: string; + admin?: boolean; }) => { const config = getConfig(); @@ -221,6 +222,7 @@ export const createNewLocalUser = async (data: { note: data.bio ?? "", avatar: data.avatar ?? config.defaults.avatar, header: data.header ?? config.defaults.avatar, + isAdmin: data.admin ?? false, uri: "", publicKey: keys.public_key, privateKey: keys.private_key, diff --git a/index.ts b/index.ts index 456b76dc..cc8bcbb3 100644 --- a/index.ts +++ b/index.ts @@ -8,6 +8,8 @@ import "reflect-metadata"; import { AuthData, getFromRequest } from "~database/entities/User"; import { APIRouteMeta } from "~types/api"; import { mkdir } from "fs/promises"; +import { client } from "~database/datasource"; +import { PrismaClientInitializationError } from "@prisma/client/runtime/library"; const router = new Bun.FileSystemRouter({ style: "nextjs", @@ -25,6 +27,20 @@ if (!(await requests_log.exists())) { await Bun.write(process.cwd() + "/logs/requests.log", ""); } +// Check if database is reachable +const postCount = 0; +try { + await client.status.count(); +} catch (e) { + const error = e as PrismaClientInitializationError; + console.error( + `${chalk.red(`✗`)} ${chalk.bold( + "Error while connecting to database: " + )} ${error.message}` + ); + process.exit(1); +} + Bun.serve({ port: config.http.bind_port, hostname: config.http.bind || "0.0.0.0", // defaults to "0.0.0.0" @@ -153,3 +169,14 @@ console.log( )}` )}` ); + +console.log( + `${chalk.green(`✓`)} ${chalk.bold(`Database is ${chalk.blue("online")}`)}` +); + +// Print "serving x posts" +console.log( + `${chalk.green(`✓`)} ${chalk.bold( + `Serving ${chalk.blue(postCount)} posts` + )}` +); diff --git a/package.json b/package.json index fb92af66..5b03fbf6 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "migrate-dev": "bunx prisma migrate dev", "migrate": "bunx prisma migrate deploy", "lint": "eslint --config .eslintrc.cjs --ext .ts .", - "generate": "bunx prisma generate" + "generate": "bunx prisma generate", + "cli": "bun run cli.ts" }, "trustedDependencies": [ "sharp", diff --git a/tests/cli.test.ts b/tests/cli.test.ts new file mode 100644 index 00000000..ca67faf5 --- /dev/null +++ b/tests/cli.test.ts @@ -0,0 +1,94 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test"; +import { client } from "~database/datasource"; +import { createNewLocalUser } from "~database/entities/User"; + +describe("cli.ts", () => { + describe("User creation", () => { + it("should execute user create command without admin flag", async () => { + afterAll(async () => { + await client.user.deleteMany({ + where: { + username: "testuser297", + email: "testuser297@gmail.com", + }, + }); + }); + + // Run command and wait for it to finish + Bun.spawnSync([ + "bun", + "run", + "cli.ts", + "user", + "create", + "testuser297", + "password123", + "testuser297@gmail.com", + ]); + + const createdUser = await client.user.findFirst({ + where: { + username: "testuser297", + email: "testuser297@gmail.com", + }, + }); + + expect(createdUser).toBeDefined(); + }); + + it("should execute user create command with admin flag", async () => { + afterAll(async () => { + await client.user.deleteMany({ + where: { + username: "testuser297", + email: "testuser297@gmail.com", + }, + }); + }); + + // Run command and wait for it to finish + Bun.spawnSync([ + "bun", + "run", + "cli.ts", + "user", + "create", + "testuser297", + "password123", + "testuser297@gmail.com", + "--admin", + ]); + + const createdUser = await client.user.findFirst({ + where: { + username: "testuser297", + email: "testuser297@gmail.com", + isAdmin: true, + }, + }); + + expect(createdUser).toBeDefined(); + }); + }); + + it("should execute user delete command", async () => { + beforeAll(async () => { + await createNewLocalUser({ + username: "bob124", + password: "jesus", + email: "bob124@bob124.com", + }); + }); + + Bun.spawnSync(["bun", "run", "cli", "user", "delete", "bob124"]); + + const userExists = await client.user.findFirst({ + where: { + username: "bob124", + email: "bob124@bob124.com", + }, + }); + + expect(!!userExists).toBe(false); + }); +});