add flash message
This commit is contained in:
@@ -52,8 +52,13 @@ def add(
|
||||
)
|
||||
|
||||
with dyn.transact_writer() as transact:
|
||||
transact.condition(
|
||||
transact.update(
|
||||
key=KeyPair(user_id, '0'),
|
||||
# Makes the email searchable
|
||||
update_expr='ADD emails :email',
|
||||
expr_attr_values={
|
||||
':email': {email},
|
||||
},
|
||||
cond_expr='attribute_exists(sk)',
|
||||
exc_cls=UserNotFoundError,
|
||||
)
|
||||
@@ -75,6 +80,7 @@ def add(
|
||||
'sk': email,
|
||||
'created_at': now_,
|
||||
},
|
||||
# Prevent duplicate emails
|
||||
cond_expr='attribute_not_exists(sk)',
|
||||
exc_cls=EmailConflictError,
|
||||
)
|
||||
@@ -172,7 +178,7 @@ def verify(user_id: str, code: str):
|
||||
exc_cls=UserNotFoundError,
|
||||
)
|
||||
|
||||
return JSONResponse(status_code=HTTPStatus.NO_CONTENT)
|
||||
return JSONResponse(status_code=HTTPStatus.OK, body={'email_verified': email})
|
||||
|
||||
|
||||
@router.patch('/<user_id>/emails/primary')
|
||||
@@ -213,13 +219,11 @@ def primary(
|
||||
)
|
||||
transact.update(
|
||||
key=KeyPair(user_id, '0'),
|
||||
update_expr='DELETE emails :email_set \
|
||||
SET email = :email, \
|
||||
update_expr='SET email = :email, \
|
||||
email_verified = :email_verified, \
|
||||
updated_at = :now',
|
||||
expr_attr_values={
|
||||
':email': new_email,
|
||||
':email_set': {new_email},
|
||||
':email_verified': email_verified,
|
||||
':now': now_,
|
||||
},
|
||||
|
||||
@@ -44,14 +44,16 @@ def test_add_email(
|
||||
)
|
||||
|
||||
assert r['statusCode'] == HTTPStatus.CREATED
|
||||
email_verification = dynamodb_persistence_layer.collection.query(
|
||||
r = dynamodb_persistence_layer.collection.query(
|
||||
KeyPair(
|
||||
'15bacf02-1535-4bee-9022-19d106fd7518',
|
||||
'EMAIL_VERIFICATION',
|
||||
)
|
||||
)
|
||||
assert 'name' in email_verification['items'][0]
|
||||
assert email_verification['items'][0]['email'] == 'osergiosiqueira+pytest@gmail.com'
|
||||
items = r['items']
|
||||
|
||||
assert len(items) == 2
|
||||
assert any(x.get('email') == 'osergiosiqueira+pytest@gmail.com' for x in items)
|
||||
|
||||
|
||||
def test_email_as_primary(
|
||||
@@ -59,6 +61,7 @@ def test_email_as_primary(
|
||||
seeds,
|
||||
http_api_proxy: HttpApiProxy,
|
||||
lambda_context: LambdaContext,
|
||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||
):
|
||||
r = app.lambda_handler(
|
||||
http_api_proxy(
|
||||
@@ -75,6 +78,12 @@ def test_email_as_primary(
|
||||
|
||||
assert r['statusCode'] == HTTPStatus.NO_CONTENT
|
||||
|
||||
r = dynamodb_persistence_layer.collection.get_item(
|
||||
KeyPair('15bacf02-1535-4bee-9022-19d106fd7518', '0')
|
||||
)
|
||||
assert r['email'] == 'osergiosiqueira@gmail.com'
|
||||
assert r['emails'] == {'osergiosiqueira@gmail.com', 'sergio@somosbeta.combr'}
|
||||
|
||||
|
||||
def test_verify_email(
|
||||
app,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Users
|
||||
{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#f6000f79-6e5c-49a0-952f-3bda330ef278", "name": "Banco do Brasil", "cnpj": "00000000000191"}
|
||||
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br", "cpf": "07879819908"}
|
||||
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br", "emails": ["osergiosiqueira@gmail.com", "sergio@somosbeta.combr"], "cpf": "07879819908"}
|
||||
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#sergio@somosbeta.com.br", "email_primary": true, "mx_record_exists": true}
|
||||
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#osergiosiqueira@gmail.com", "email_verified": false, "mx_record_exists": true}
|
||||
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "EMAIL_VERIFICATION#0d29c753-55f8-42d2-908b-e4976aafc183", "email": "osergiosiqueira@gmail.com", "name": "Sérgio Rafael de Siqueira"}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Route } from './+types/layout'
|
||||
import { useToggle } from 'ahooks'
|
||||
import { MenuIcon } from 'lucide-react'
|
||||
import { Link, NavLink, Outlet } from 'react-router'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Toaster } from '@repo/ui/components/ui/sonner'
|
||||
import { userContext } from '@repo/auth/context'
|
||||
@@ -23,12 +24,31 @@ import {
|
||||
SheetTrigger
|
||||
} from '@repo/ui/components/ui/sheet'
|
||||
import type { User } from '@repo/auth/auth'
|
||||
import { createSessionStorage } from '@repo/auth/session'
|
||||
import { data } from 'react-router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
|
||||
|
||||
export async function loader({ context }: Route.ActionArgs) {
|
||||
export async function loader({ context, request }: Route.ActionArgs) {
|
||||
const user = context.get(userContext) as User
|
||||
return Response.json({ user })
|
||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||
|
||||
const flash = {
|
||||
error: session.get('error'),
|
||||
success: session.get('success'),
|
||||
info: session.get('info')
|
||||
}
|
||||
|
||||
return data(
|
||||
{ user, flash },
|
||||
{
|
||||
headers: new Headers({
|
||||
'Set-Cookie': await sessionStorage.commitSession(session)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const navMain = [
|
||||
@@ -47,10 +67,16 @@ const navMain = [
|
||||
]
|
||||
|
||||
export default function Component({
|
||||
loaderData: { user }
|
||||
loaderData: { user, flash }
|
||||
}: Route.ComponentProps) {
|
||||
const [isOpen, { toggle }] = useToggle()
|
||||
|
||||
useEffect(() => {
|
||||
if (flash.error) toast.error(flash.error)
|
||||
if (flash.success) toast.success(flash.success)
|
||||
if (flash.info) toast.info(flash.info)
|
||||
}, [flash])
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col flex-1 min-w-0 h-full">
|
||||
<header
|
||||
|
||||
@@ -42,7 +42,6 @@ import { request as req } from '@repo/util/request'
|
||||
import { Skeleton } from '@repo/ui/components/skeleton'
|
||||
import { Button } from '@repo/ui/components/ui/button'
|
||||
import { useOutletContext } from 'react-router'
|
||||
import type { User as AuthUser } from '@repo/auth/auth'
|
||||
import type { User } from '@repo/ui/routes/users/data'
|
||||
import {
|
||||
Item,
|
||||
@@ -59,7 +58,8 @@ import { Primary } from './primary'
|
||||
const ActionMenuContext = createContext<Email | null>(null)
|
||||
|
||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||
const user = context.get(userContext) as AuthUser
|
||||
const user = context.get(userContext)
|
||||
|
||||
const data = req({
|
||||
url: `/users/${user.sub}/emails`,
|
||||
request,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Route } from './+types/verify'
|
||||
|
||||
import { redirect } from 'react-router'
|
||||
|
||||
import { userContext } from '@repo/auth/context'
|
||||
import { createSessionStorage } from '@repo/auth/session'
|
||||
import { HttpMethod, request as req } from '@repo/util/request'
|
||||
|
||||
export async function loader({ params, request, context }: Route.LoaderArgs) {
|
||||
const { code } = params
|
||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||
const user = context.get(userContext)
|
||||
|
||||
const r = await req({
|
||||
url: `/users/${user.sub}/emails/${code}/verify`,
|
||||
method: HttpMethod.POST,
|
||||
request,
|
||||
context
|
||||
})
|
||||
|
||||
if (r.ok) {
|
||||
session.flash('success', 'Seu email foi verificado.')
|
||||
} else {
|
||||
session.flash('info', 'O email já está verificado.')
|
||||
}
|
||||
|
||||
return redirect('/settings/emails', {
|
||||
headers: new Headers({
|
||||
'Set-Cookie': await sessionStorage.commitSession(session)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export const authMiddleware = async (
|
||||
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
|
||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||
const requestId = context.get(requestIdContext)
|
||||
let user = session.get('user') as User | null
|
||||
let user = session.get('user')
|
||||
|
||||
if (!user) {
|
||||
console.log('There is no user logged in')
|
||||
@@ -67,7 +67,11 @@ export const authMiddleware = async (
|
||||
context.set(userContext, user)
|
||||
|
||||
const response = await next()
|
||||
const sessionCookie = await sessionStorage.commitSession(session)
|
||||
response.headers.set('Set-Cookie', sessionCookie)
|
||||
|
||||
if (!response.headers.has('Set-Cookie')) {
|
||||
const sessionCookie = await sessionStorage.commitSession(session)
|
||||
response.headers.set('Set-Cookie', sessionCookie)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import { createCookieSessionStorage } from 'react-router'
|
||||
import { type User } from './auth'
|
||||
|
||||
export function createSessionStorage(env) {
|
||||
const sessionStorage = createCookieSessionStorage({
|
||||
type SessionData = {
|
||||
user: User
|
||||
returnTo: string
|
||||
}
|
||||
|
||||
type SessionFlashData = {
|
||||
error: string
|
||||
success: string
|
||||
info: string
|
||||
}
|
||||
|
||||
export function createSessionStorage(env: any) {
|
||||
const cookieSessionStorage = createCookieSessionStorage<
|
||||
SessionData,
|
||||
SessionFlashData
|
||||
>({
|
||||
cookie: {
|
||||
name: '__session',
|
||||
httpOnly: true,
|
||||
@@ -12,6 +27,5 @@ export function createSessionStorage(env) {
|
||||
maxAge: 86400 * 7 // 7 days
|
||||
}
|
||||
})
|
||||
|
||||
return sessionStorage
|
||||
return cookieSessionStorage
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user