Skip to content

Fix/username taken #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions app/models/profile.server.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -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<PostgresJsQueryResultHKT, typeof schema, ExtractTablesWithRelations<typeof schema>>,
userId: User["id"],
username: Profile["username"],
) {
return transaction
.insert(profile)
.values({
username,
public: false,
userId,
});
}
42 changes: 23 additions & 19 deletions app/models/user.server.ts
Original file line number Diff line number Diff line change
@@ -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";

Check warning on line 4 in app/models/user.server.ts

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'postgres' is defined but never used. Allowed unused vars must match /^ignored/u
import { createProfileWithTransaction } from "./profile.server";
import { drizzleClient } from "~/db.server";
import { type Password, type User, password as passwordTable, user } from "~/schema";

Expand All @@ -18,6 +19,12 @@
});
}

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 },
Expand Down Expand Up @@ -106,25 +113,22 @@
) {
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(
Expand Down
51 changes: 27 additions & 24 deletions app/routes/explore.register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -35,7 +35,7 @@ export async function action({ request }: ActionFunctionArgs) {
return data(
{
errors: {
username: "UserName is required",
username: "username_required",
email: null,
password: null,
},
Expand All @@ -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 },
);
}
Expand All @@ -71,7 +78,7 @@ export async function action({ request }: ActionFunctionArgs) {
{
errors: {
username: null,
password: "Password is required",
password: "password_required",
email: null,
},
},
Expand All @@ -84,7 +91,7 @@ export async function action({ request }: ActionFunctionArgs) {
{
errors: {
username: null,
password: "Password is too short",
password: "password_too_short",
email: null,
},
},
Expand All @@ -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,
},
},
Expand All @@ -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,
Expand Down Expand Up @@ -168,34 +171,34 @@ export default function RegisterDialog() {
)}
<Form method="post" className="space-y-6" noValidate>
<CardHeader>
<CardTitle className="text-2xl font-bold">Register</CardTitle>
<CardTitle className="text-2xl font-bold">{t('register')}</CardTitle>
<CardDescription>
Create a new account to get started.
{t('create_account')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Label htmlFor="username">{t('username')}</Label>
<Input
id="username"
placeholder="Enter your username"
placeholder={t('enter_username')}
ref={usernameRef}
name="username"
type="text"
autoFocus={true}
/>
{actionData?.errors?.username && (
<div className="text-sm text-red-500 mt-1" id="password-error">
{actionData.errors.username}
{t(actionData.errors.username)}
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">{t("email_label")}</Label>
<Label htmlFor="email">{t("email")}</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
placeholder={t('enter_email')}
ref={emailRef}
required
autoFocus={true}
Expand All @@ -206,16 +209,16 @@ export default function RegisterDialog() {
/>
{actionData?.errors?.email && (
<div className="text-sm text-red-500 mt-1" id="email-error">
{actionData.errors.email}
{t(actionData.errors.email)}
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">{t("password_label")}</Label>
<Label htmlFor="password">{t("password")}</Label>
<Input
id="password"
type="password"
placeholder="Enter your password"
placeholder={t('enter_password')}
ref={passwordRef}
name="password"
autoComplete="new-password"
Expand All @@ -224,17 +227,17 @@ export default function RegisterDialog() {
/>
{actionData?.errors?.password && (
<div className="text-sm text-red-500 mt-1" id="password-error">
{actionData.errors.password}
{t(actionData.errors.password)}
</div>
)}
</div>
</CardContent>
<CardFooter className="flex flex-col items-center gap-2">
<Button className="w-full bg-light-blue">Register</Button>
<Button className="w-full bg-light-blue">{t('register')}</Button>
<div className="text-sm text-muted-foreground">
{t("already_account_label")}{" "}
{t("already_account")}{" "}
<Link to="/explore/login" className="underline">
{t("login_label")}
{t("login")}
</Link>
</div>
</CardFooter>
Expand Down
6 changes: 3 additions & 3 deletions app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
27 changes: 20 additions & 7 deletions public/locales/de/register.json
Original file line number Diff line number Diff line change
@@ -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"
}
27 changes: 20 additions & 7 deletions public/locales/en/register.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading