Skip to content

Commit

Permalink
CRUD and routers for EmailSignUpToken, two part register flow almost …
Browse files Browse the repository at this point in the history
…done
  • Loading branch information
Winston-Hsiao committed Aug 10, 2024
1 parent 676d3a6 commit a15db8d
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 27 deletions.
43 changes: 31 additions & 12 deletions frontend/src/components/auth/SignupWithEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
import { SubmitHandler, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";

import { zodResolver } from "@hookform/resolvers/zod";
import { EmailSignUpSchema, EmailSignUpType } from "types";
import { useAlertQueue } from "hooks/useAlertQueue";
import { useAuthentication } from "hooks/useAuth";
import { EmailSignupSchema, EmailSignupType } from "types";

import { Button } from "components/ui/Button/Button";
import ErrorMessage from "components/ui/ErrorMessage";
import { Input } from "components/ui/Input/Input";

interface EmailSignUpResponse {
message: string;
}

const SignupWithEmail = () => {
const auth = useAuthentication();
const { addAlert, addErrorAlert } = useAlertQueue();

const {
register,
handleSubmit,
formState: { errors },
} = useForm<EmailSignupType>({
resolver: zodResolver(EmailSignUpSchema),
resolver: zodResolver(EmailSignupSchema),
});

const onSubmit: SubmitHandler<EmailSignupType> = async (
data: EmailSignupType,
) => {
// TODO: Add an api endpoint to create EmailSignUpToken and send email to
// submitted email. User gets link in email to /register/{token}
// to finish sign up
const onSubmit = async ({ email }: EmailSignupType) => {
console.log(`email: ${email}`);
const { data, error } = await auth.client.POST("/email-signup/create/", {
body: {
email,
},
});

console.log(data);
if (error) {
addErrorAlert(error);
} else {
const responseData = data as EmailSignUpResponse;
const successMessage =
responseData?.message || "Sign-up email sent! Check your inbox.";
addAlert(successMessage, "success");
}
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="grid grid-cols-1 space-y-6">
className="grid grid-cols-1 space-y-6"
>
{/* Email Input */}
<div className="relative">
<Input placeholder="Email" type="text" {...register("email")} />
Expand All @@ -38,7 +56,8 @@ const SignupWithEmail = () => {
{/* Signup Button */}
<Button
variant="outline"
className="w-full text-white bg-blue-600 hover:bg-opacity-70">
className="w-full text-white bg-blue-600 hover:bg-opacity-70"
>
Sign up with email
</Button>
</form>
Expand Down
171 changes: 171 additions & 0 deletions frontend/src/gen/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,57 @@ export interface paths {
patch?: never;
trace?: never;
};
"/email-signup/create/": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Create Signup Token */
post: operations["create_signup_token_email_signup_create__post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/email-signup/get/{token}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Signup Token */
get: operations["get_signup_token_email_signup_get__token__get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/email-signup/delete/{token}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Delete Signup Token */
delete: operations["delete_signup_token_email_signup_delete__token__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
Expand All @@ -376,11 +427,29 @@ export interface components {
/** Metadata */
metadata: string;
};
/** DeleteTokenResponse */
DeleteTokenResponse: {
/** Message */
message: string;
};
/** DumpListingsResponse */
DumpListingsResponse: {
/** Listings */
listings: components["schemas"]["Listing"][];
};
/** EmailSignUpRequest */
EmailSignUpRequest: {
/**
* Email
* Format: email
*/
email: string;
};
/** EmailSignUpResponse */
EmailSignUpResponse: {
/** Message */
message: string;
};
/** GetBatchListingsResponse */
GetBatchListingsResponse: {
/** Listings */
Expand All @@ -401,6 +470,13 @@ export interface components {
/** Owner Is User */
owner_is_user: boolean;
};
/** GetTokenResponse */
GetTokenResponse: {
/** Email */
email: string;
/** Created At */
created_at: string;
};
/** GithubAuthRequest */
GithubAuthRequest: {
/** Code */
Expand Down Expand Up @@ -1163,4 +1239,99 @@ export interface operations {
};
};
};
create_signup_token_email_signup_create__post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["EmailSignUpRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["EmailSignUpResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_signup_token_email_signup_get__token__get: {
parameters: {
query?: never;
header?: never;
path: {
token: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["GetTokenResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_signup_token_email_signup_delete__token__delete: {
parameters: {
query?: never;
header?: never;
path: {
token: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["DeleteTokenResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}
18 changes: 11 additions & 7 deletions frontend/src/pages/Register.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";

import { paths } from "gen/api";
import { useAlertQueue } from "hooks/useAlertQueue";
Expand All @@ -9,7 +9,7 @@ import SignupForm from "components/auth/SignupForm";
import { Button } from "components/ui/Button/Button";

type EmailSignUpResponse =
paths["/emailSignUp/{token}"]["get"]["responses"][200]["content"]["application/json"];
paths["/email-signup/get/{token}"]["get"]["responses"][200]["content"]["application/json"];

const Register = () => {
const { addErrorAlert } = useAlertQueue();
Expand All @@ -26,11 +26,14 @@ const Register = () => {
}

try {
const { data, error } = await auth.client.GET("/emailSignUp/{token}", {
params: {
path: { token },
const { data, error } = await auth.client.GET(
"/email-signup/get/{token}",
{
params: {
path: { token },
},
},
});
);
if (error) {
addErrorAlert(error);
} else {
Expand All @@ -49,7 +52,8 @@ const Register = () => {
<h1>Invalid Sign Up Link</h1>
<Button
variant="outline"
className="w-full text-white bg-blue-600 hover:bg-opacity-70">
className="w-full text-white bg-blue-600 hover:bg-opacity-70"
>
Login / Signup
</Button>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const LoginSchema = z.object({

export type LoginType = z.infer<typeof LoginSchema>;

export const EmailSignUpSchema = z.object({
export const EmailSignupSchema = z.object({
email: z
.string({
required_error: "Email required.",
Expand All @@ -27,7 +27,7 @@ export const EmailSignUpSchema = z.object({
.email("Invalid email."),
});

export type EmailSignUpType = z.infer<typeof EmailSignUpSchema>;
export type EmailSignupType = z.infer<typeof EmailSignupSchema>;

export const SignUpSchema = z
.object({
Expand Down
25 changes: 25 additions & 0 deletions store/app/crud/email_signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""This module provides CRUD operations for email sign-up tokens."""

from store.app.crud.base import BaseCrud
from store.app.model import EmailSignUpToken


class EmailSignUpCrud(BaseCrud):
async def create_email_signup_token(self, email: str) -> EmailSignUpToken:
token = EmailSignUpToken.create(email=email)
await self._add_item(token)
return token

async def get_email_signup_token(self, token: str) -> EmailSignUpToken | None:
return await self._get_item(token, EmailSignUpToken, throw_if_missing=False)

async def delete_email_signup_token(self, token: str) -> None:
await self._delete_item(token)


async def test_adhoc() -> None:
async with EmailSignUpCrud() as crud:
token = await crud.create_email_signup_token(email="[email protected]")
retrieved_token = await crud.get_email_signup_token(token.token)
print(f"Retrieved Token: {retrieved_token}")
await crud.delete_email_signup_token(token.token)
9 changes: 8 additions & 1 deletion store/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
from fastapi.responses import JSONResponse

from store.app.db import create_tables
from store.app.errors import InternalError, ItemNotFoundError, NotAuthenticatedError, NotAuthorizedError
from store.app.errors import (
InternalError,
ItemNotFoundError,
NotAuthenticatedError,
NotAuthorizedError,
)
from store.app.routers.artifacts import artifacts_router
from store.app.routers.email_signup import email_signup_router
from store.app.routers.listings import listings_router
from store.app.routers.users import users_router
from store.settings import settings
Expand Down Expand Up @@ -93,6 +99,7 @@ async def read_root() -> bool:
app.include_router(users_router, prefix="/users", tags=["users"])
app.include_router(listings_router, prefix="/listings", tags=["listings"])
app.include_router(artifacts_router, prefix="/artifacts", tags=["artifacts"])
app.include_router(email_signup_router, prefix="/email-signup", tags=["email-signup"])

# For running with debugger
if __name__ == "__main__":
Expand Down
Loading

0 comments on commit a15db8d

Please sign in to comment.