ls-deno/routes/register.tsx

118 lines
3.5 KiB
TypeScript

import { Handlers, PageProps } from "$fresh/server.ts";
import { Page } from "@/components/Page.tsx";
import { PostgresError, query } from "@/db/mod.ts";
import { hash } from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
type UserID = string;
interface RegistrationError {
message: string;
}
export const handler: Handlers<UserID | RegistrationError | null> = {
async POST(request, context) {
const formData = (await request.formData());
const username = formData.get("username");
const password = formData.get("password");
// TODO: verify that username conforms to some regex? no spaces?
if (!username) {
return await context.render({ message: "no username provided" });
}
if (!password) {
return await context.render({ message: "no password provided" });
}
const password_digest = await hash(password.toString());
try {
const result = await query<{ user_id: string }>(
`
with new_user as (
insert into "user" (username, password_digest, status)
values ($username, $password_digest, 'unverified')
returning id as user_id
), new_team as (
insert into "team" (display_name)
values ($team_name)
returning id as team_id
)
insert into "team_user" (user_id, team_id, status)
values (
(select user_id from new_user),
(select team_id from new_team),
'owner'
) returning user_id
`,
{ username, password_digest, team_name: `${username}'s First Team` },
);
console.debug(result);
if (!result) throw "insert failed";
const { rows: [{ user_id: id }] } = result;
return await context.render(id);
} catch (err) {
console.log("Error fields:", { ...err });
if (
err instanceof PostgresError && err.fields.code == "23505" &&
err.fields.constraint == "user_username_key"
) {
return await context.render({
message: `A user with username '${username}' already exists`,
});
} else if (
err instanceof PostgresError && err.fields.code == "23514" &&
err.fields.constraint == "valid_username"
) {
return await context.render({
message:
`Username must ONLY be comprised of letters, number, dashes, and underscores`,
});
}
throw err;
}
},
};
export default function Register(
{ data: userId }: PageProps<UserID | RegistrationError | null>,
) {
if (typeof userId == "string") {
return RegistrationSuccessful(userId);
} else {
return RegistrationForm(userId);
}
}
function RegistrationSuccessful(_userId: UserID) {
return (
<Page>
<p>
You're all signed up! Let's go <a href="/login">log in</a>!
</p>
</Page>
);
}
function RegistrationForm(props?: RegistrationError | null) {
console.log(props);
return (
<Page>
<h1 class="text-4xl mb-4">Register your account</h1>
{props != null &&
(
<p class="text-red-500">
<strong>Error</strong>: {props.message}
</p>
)}
<form class="flex flex-col max-w-lg" method="post">
<label for="username">Username</label>
<input type="text" name="username" />
<label for="password">Password</label>
<input type="password" name="password" />
<input
class="bg-blue-800 px-4 p-2 mt-2"
type="submit"
value="Register"
/>
</form>
</Page>
);
}