diff --git a/app/models/profile.server.ts b/app/models/profile.server.ts index f61ea6bc..dc4f6ac2 100644 --- a/app/models/profile.server.ts +++ b/app/models/profile.server.ts @@ -1,6 +1,9 @@ -import { eq } from "drizzle-orm"; +import { eq, type ExtractTablesWithRelations } from "drizzle-orm"; +import { type PgTransaction } from "drizzle-orm/pg-core"; +import { type PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"; import { drizzleClient } from "~/db.server"; -import { type User, type Profile, profile } from "~/schema"; +import { type User, type Profile, profile } from "~/schema"; +import type * as schema from "~/schema"; export async function getProfileByUserId(id: Profile["id"]) { return drizzleClient.query.profile.findFirst({ @@ -45,9 +48,20 @@ export async function createProfile( userId: User["id"], username: Profile["username"], ) { - return drizzleClient.insert(profile).values({ - username, - public: false, - userId, - }); + return drizzleClient.transaction(t => + createProfileWithTransaction(t, userId, username)); } + +export async function createProfileWithTransaction( + transaction: PgTransaction>, + userId: User["id"], + username: Profile["username"], +) { + return transaction + .insert(profile) + .values({ + username, + public: false, + userId, + }); +} \ No newline at end of file diff --git a/app/models/user.server.ts b/app/models/user.server.ts index 9ad6a62a..1bb99c6f 100644 --- a/app/models/user.server.ts +++ b/app/models/user.server.ts @@ -1,7 +1,8 @@ import crypto from "node:crypto"; import bcrypt from "bcryptjs"; import { eq } from "drizzle-orm"; -import { createProfile } from "./profile.server"; +import postgres from "postgres"; +import { createProfileWithTransaction } from "./profile.server"; import { drizzleClient } from "~/db.server"; import { type Password, type User, password as passwordTable, user } from "~/schema"; @@ -18,6 +19,12 @@ export async function getUserByEmail(email: User["email"]) { }); } +export async function getUserByUsername(username: User["name"]) { + return drizzleClient.query.user.findFirst({ + where: (user, { eq }) => eq(user.name, username), + }); +} + // export async function getUserWithDevicesByName(name: User["name"]) { // return prisma.user.findUnique({ // where: { name }, @@ -106,25 +113,22 @@ export async function createUser( ) { const hashedPassword = await bcrypt.hash(preparePasswordHash(password), 13); // make salt_factor configurable oSeM API uses 13 by default - // Maybe wrap in a transaction - // https://stackoverflow.com/questions/76082778/drizzle-orm-how-do-you-insert-in-a-parent-and-child-table - const newUser = await drizzleClient - .insert(user) - .values({ - name, - email, - language, - }) - .returning(); - - await drizzleClient.insert(passwordTable).values({ - hash: hashedPassword, - userId: newUser[0].id, + return await drizzleClient.transaction(async t => { + const newUser = await t.insert(user) + .values({ + name, + email, + language, + }) + .returning(); + await t.insert(passwordTable) + .values({ + hash: hashedPassword, + userId: newUser[0].id, + }); + await createProfileWithTransaction(t, newUser[0].id, name); + return newUser; }); - - await createProfile(newUser[0].id, name); - - return newUser; } export async function verifyLogin( diff --git a/app/routes/explore.register.tsx b/app/routes/explore.register.tsx index 9a5cee83..1284bf8d 100644 --- a/app/routes/explore.register.tsx +++ b/app/routes/explore.register.tsx @@ -16,7 +16,7 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; -import { createUser, getUserByEmail } from "~/models/user.server"; +import { createUser, getUserByEmail, getUserByUsername } from "~/models/user.server"; import { safeRedirect, validateEmail, validateName } from "~/utils"; import { createUserSession, getUserId } from "~/utils/session.server"; @@ -35,7 +35,7 @@ export async function action({ request }: ActionFunctionArgs) { return data( { errors: { - username: "UserName is required", + username: "username_required", email: null, password: null, }, @@ -59,9 +59,16 @@ export async function action({ request }: ActionFunctionArgs) { ); } + const existingUsername = await getUserByUsername(username); + if(existingUsername) + return data( + { errors: { username: "username_already_taken", email: null, password: null } }, + { status: 400 }, + ); + if (!validateEmail(email)) { return data( - { errors: { username: null, email: "Email is invalid", password: null } }, + { errors: { username: null, email: "email_invalid", password: null } }, { status: 400 }, ); } @@ -71,7 +78,7 @@ export async function action({ request }: ActionFunctionArgs) { { errors: { username: null, - password: "Password is required", + password: "password_required", email: null, }, }, @@ -84,7 +91,7 @@ export async function action({ request }: ActionFunctionArgs) { { errors: { username: null, - password: "Password is too short", + password: "password_too_short", email: null, }, }, @@ -99,7 +106,7 @@ export async function action({ request }: ActionFunctionArgs) { { errors: { username: null, - email: "A user already exists with this email", + email: "email_already_taken", password: null, }, }, @@ -113,11 +120,7 @@ export async function action({ request }: ActionFunctionArgs) { const locale = await i18next.getLocale(request); const language = locale === "de" ? "de_DE" : "en_US"; - //* temp -> dummy name - // const name = "Max Mustermann"; - const user = await createUser(username, email, language, password); - // const user = await createUser(email, password, username?.toString()); return createUserSession({ request, @@ -168,17 +171,17 @@ export default function RegisterDialog() { )}
- Register + {t('register')} - Create a new account to get started. + {t('create_account')}
- + {actionData?.errors?.username && (
- {actionData.errors.username} + {t(actionData.errors.username)}
)}
- + {actionData?.errors?.email && (
- {actionData.errors.email} + {t(actionData.errors.email)}
)}
- + {actionData?.errors?.password && (
- {actionData.errors.password} + {t(actionData.errors.password)}
)}
- +
- {t("already_account_label")}{" "} + {t("already_account")}{" "} - {t("login_label")} + {t("login")}
diff --git a/app/utils.ts b/app/utils.ts index 7dbf704c..d82dd6af 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -94,14 +94,14 @@ export function validateEmail(email: unknown): email is string { //* validate user name in join page export function validateName(name: string) { if (name.length === 0) { - return { isValid: false, errorMsg: "Name is required" }; + return { isValid: false, errorMsg: "username_required" }; } else if (name.length < 4) { - return { isValid: false, errorMsg: "Please use at least 4 characters." }; + return { isValid: false, errorMsg: "username_min_characters" }; } else if ( name && !/^[a-zA-Z0-9][a-zA-Z0-9\s._-]+[a-zA-Z0-9-_.]$/.test(name.toString()) ) { - return { isValid: false, errorMsg: "Name is invalid" }; + return { isValid: false, errorMsg: "username_invalid" }; } return { isValid: true }; diff --git a/public/locales/de/register.json b/public/locales/de/register.json index 5f087ae9..417dbd55 100644 --- a/public/locales/de/register.json +++ b/public/locales/de/register.json @@ -1,9 +1,22 @@ { - "register_label": "Registrieren", - "email_label": "E-Mail", - "password_label": "Passwort", - "account_label": "Konto erstellen", - "transition_label": "Erstelle Konto...", - "already_account_label": "Bereits ein Konto?", - "login_label": "Einloggen" + "register": "Registrieren", + "create_account": "Um loszulegen, erstelle einen neuen Account.", + "username": "Benutzername", + "enter_username": "Gib deinen Benutzernamen ein", + "email": "E-Mail", + "enter_email": "Gib deine E-Mail Adresse ein", + "password": "Passwort", + "enter_password": "Gib dein Passwort ein", + "account": "Konto erstellen", + "transition": "Erstelle Konto...", + "already_account": "Bereits ein Konto?", + "login": "Einloggen", + "username_required": "Benutzername ist ein Pflichtfeld", + "username_already_taken": "Der Benutzername ist bereits vergeben", + "username_min_characters": "Bitte nutze mindestens 4 Zeichen", + "username_invalid": "Der Benutzername beinhaltet ungültige Zeichen", + "password_required": "Passwort ist ein Pflichtfeld", + "password_too_short": "Passwort ist zu kurz", + "email_already_taken": "Die E-Mail Adresse wird bereits verwendet", + "email_invalid": "Ungültige E-Mail Adresse" } \ No newline at end of file diff --git a/public/locales/en/register.json b/public/locales/en/register.json index 3864f0cb..ca57cb0e 100644 --- a/public/locales/en/register.json +++ b/public/locales/en/register.json @@ -1,9 +1,22 @@ { - "register_label": "Sign up", - "email_label": "Email", - "password_label": "Password", - "account_label": "Create account", - "transition_label": "Creating account...", - "already_account_label": "Already have an account?", - "login_label": "Log in" + "register": "Sign up", + "create_account": "Create a new account to get started.", + "username": "Username", + "enter_username": "Enter your username", + "email": "Email", + "enter_email": "Enter your email address", + "password": "Password", + "enter_password": "Enter your password", + "account": "Create account", + "transition": "Creating account...", + "already_account": "Already have an account?", + "login": "Log in", + "username_required": "Username is required", + "username_already_taken": "This username is already taken", + "username_min_characters": "Please use at least 4 characters.", + "username_invalid": "Username has invalid characters", + "password_required": "Password is required", + "password_too_short": "Password is too short", + "email_already_taken": "A user with this email already exists", + "email_invalid": "Invalid email address" } \ No newline at end of file