summaryrefslogtreecommitdiff
path: root/src/routes/signup.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/signup.ts')
-rw-r--r--src/routes/signup.ts100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/routes/signup.ts b/src/routes/signup.ts
new file mode 100644
index 0000000..ae05e4f
--- /dev/null
+++ b/src/routes/signup.ts
@@ -0,0 +1,100 @@
+import argon2 from "argon2";
+import express from "express";
+import { users } from "../db/schema";
+import { db } from "../db/db";
+import { Infer, z } from "zod";
+import { signJwt } from "../account/jwt";
+import { eq } from "drizzle-orm";
+import { verificationEmailQueue } from "../queue/email";
+import { logger } from "../lib/pino";
+
+/** Validates signup request with email and password fields */
+const SignupBodySchema = z.object({
+ email: z
+ .email({ error: "Invalid email" })
+ .nonempty({ error: "Email can't be empty" }),
+ password: z.string(),
+});
+
+/**
+ * Generates a random 6-digit verification code.
+ * Used for email verification during signup.
+ */
+export function generate6DigitCode(): string {
+ return Math.floor(Math.random() * 1_000_000)
+ .toString()
+ .padStart(6, "0");
+}
+
+const router: express.Router = express.Router();
+
+router.post("/signup", async (req, res) => {
+ const body: Infer<typeof SignupBodySchema> = req.body;
+ logger.info({ email: body.email }, "Signup attempt initiated");
+
+ const parsed = SignupBodySchema.safeParse(body);
+ if (!parsed.success) {
+ logger.warn(
+ { email: body.email, error: parsed.error },
+ "Signup validation failed",
+ );
+ return res.send({
+ success: false,
+ reason: parsed.error,
+ token: "",
+ });
+ }
+
+ const existingUsers = await db
+ .select()
+ .from(users)
+ .where(eq(users.email, body.email));
+
+ if (existingUsers.length > 0) {
+ logger.warn({ email: body.email }, "Signup failed: email already in use");
+ return res.send({
+ success: false,
+ reason: "Email already used!",
+ });
+ }
+
+ const hashedPassword = await argon2.hash(body.password);
+ const code = generate6DigitCode();
+
+ const user = await db
+ .insert(users)
+ .values({
+ email: body.email,
+ password: hashedPassword,
+ emailCode: code,
+ })
+ .returning();
+
+ await verificationEmailQueue.add("sendVerificationEmail", {
+ email: body.email,
+ code,
+ });
+
+ logger.info(
+ { userId: user[0]!.id, email: body.email },
+ "Verification email queued",
+ );
+
+ const jwt = await signJwt(
+ { id: user[0]!.id, type: "parent" },
+ "urn:buddy:users",
+ );
+
+ logger.info(
+ { userId: user[0]!.id, email: body.email },
+ "User signup completed successfully",
+ );
+
+ res.send({
+ success: true,
+ token: jwt,
+ reason: "",
+ });
+});
+
+export default router;