studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
152 lines (151 loc) • 5.38 kB
JavaScript
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
};