UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

152 lines (151 loc) 5.38 kB
import { getAvatarUrl } from "@withstudiocms/auth-kit/utils/libravatar"; import { StudioCMSColorwayInfo } from "@withstudiocms/cli-kit/colors"; import { group, log, password, select, text } from "@withstudiocms/effect/clack"; import { z } from "astro/zod"; import { Effect, runEffect } from "../../../../effect.js"; import { buildDebugLogger } from "../../../utils/logger.js"; import { libSQLDrizzleClient, Permissions, Users } from "../../../utils/useLibSQLDb.js"; import { getCheckers, hashPassword, verifyPasswordStrength } from "../../../utils/user-utils.js"; const libsqlCreateUsers = Effect.fn(function* (context, debug, dryRun) { const [checker, debugLogger] = yield* Effect.all([getCheckers, buildDebugLogger(debug)]); const { ASTRO_DB_REMOTE_URL, ASTRO_DB_APP_TOKEN } = process.env; const [_drop, db] = yield* Effect.all([ debugLogger("Running libsqlUsers..."), libSQLDrizzleClient(ASTRO_DB_REMOTE_URL, ASTRO_DB_APP_TOKEN) ]); const currentUsers = yield* db.execute((db2) => db2.select().from(Users)); const inputData = yield* group( { username: async () => await runEffect( text({ message: "Username", placeholder: "johndoe", validate: (user) => { const u = user.trim(); const isUser = currentUsers.find(({ username: username2 }) => username2 === u); if (isUser) return "Username is already in use, please try another one"; if (Effect.runSync(checker.username(user))) { return "Username should not be a commonly used unsafe username (admin, root, etc.)"; } return void 0; } }) ), name: async () => await runEffect( text({ message: "Display Name", placeholder: "John Doe" }) ), email: async () => await runEffect( text({ message: "E-Mail Address", placeholder: "john@doe.tld", validate: (email2) => { const e = email2.trim().toLowerCase(); const emailSchema = z.string().email({ message: "Email address is invalid" }); const response = emailSchema.safeParse(e); if (!response.success) return response.error.message; if (currentUsers.find((user) => user.email === e)) { return "There is already a user with that email."; } return void 0; } }) ), newPassword: async () => await runEffect( password({ message: "Password", validate: (password2) => { const passCheck = Effect.runSync(verifyPasswordStrength(password2)); if (passCheck !== true) { return passCheck; } return void 0; } }) ), confirmPassword: async () => await runEffect( password({ message: "Confirm Password" }) ), rank: async () => await runEffect( select({ message: "What Role should this user have?", options: [ { value: "visitor", label: "Visitor" }, { value: "editor", label: "Editor" }, { value: "admin", label: "Admin" }, { value: "owner", label: "Owner" } ] }) ) }, { onCancel: async () => await runEffect(context.pOnCancel()) } ); const { confirmPassword, email, name, newPassword, rank, username } = inputData; if (newPassword !== confirmPassword) { yield* log.error(context.chalk.red("Passwords do not match, exiting...")); return yield* context.exit(1); } const newUserId = crypto.randomUUID(); const [hashedPassword, avatar] = yield* Effect.all([ hashPassword(newPassword), Effect.tryPromise({ try: () => getAvatarUrl({ email, https: true, size: 400, default: "retro" }), catch: (cause) => new Error("Failed to fetch avatar URL", { cause }) }) ]); const newUser = { id: newUserId, name, username, email, avatar, password: hashedPassword, createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }; const newRank = { user: newUserId, rank }; if (dryRun) { context.tasks.push({ title: `${StudioCMSColorwayInfo.bold("--dry-run")} ${context.chalk.dim("Skipping user creation")}`, task: async (message) => { message("Creating user... (skipped)"); } }); } else { context.tasks.push({ title: context.chalk.dim("Creating user..."), task: async (message) => { try { const [insertedUser, insertedRank] = await runEffect( db.execute( (tx) => tx.batch([ tx.insert(Users).values(newUser).returning(), tx.insert(Permissions).values(newRank).returning() ]) ) ); if (insertedUser.length === 0 || insertedRank.length === 0) { message("Failed to create user or assign permissions"); return await runEffect(context.exit(1)); } message(context.chalk.green("User created successfully!")); } catch (error) { await runEffect(log.error(`Failed to create user: ${error.message}`)); return await runEffect(context.exit(1)); } } }); } }); export { libsqlCreateUsers };