add user to org
This commit is contained in:
@@ -4,14 +4,14 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from aws_lambda_powertools.event_handler.exceptions import (
|
from aws_lambda_powertools.event_handler.exceptions import (
|
||||||
BadRequestError,
|
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
ServiceError,
|
||||||
)
|
)
|
||||||
from aws_lambda_powertools.event_handler.openapi.params import Body
|
from aws_lambda_powertools.event_handler.openapi.params import Body
|
||||||
from layercake.dateutils import now
|
from layercake.dateutils import now
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
|
||||||
from layercake.extra_types import CnpjStr, CpfStr, NameStr
|
from layercake.extra_types import CnpjStr, CpfStr, NameStr
|
||||||
from pydantic import UUID4, BaseModel, EmailStr, Field
|
from pydantic import BaseModel, EmailStr
|
||||||
|
|
||||||
from api_gateway import JSONResponse
|
from api_gateway import JSONResponse
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
@@ -28,7 +28,6 @@ class Org(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
id: UUID4 = Field(default_factory=uuid4)
|
|
||||||
name: NameStr
|
name: NameStr
|
||||||
cpf: CpfStr
|
cpf: CpfStr
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
@@ -40,7 +39,9 @@ class CPFConflictError(Exception): ...
|
|||||||
class EmailConflictError(Exception): ...
|
class EmailConflictError(Exception): ...
|
||||||
|
|
||||||
|
|
||||||
class UserConflictError(BadRequestError): ...
|
class UserConflictError(ServiceError):
|
||||||
|
def __init__(self, msg: str | dict):
|
||||||
|
super().__init__(HTTPStatus.CONFLICT, msg)
|
||||||
|
|
||||||
|
|
||||||
class UserNotFoundError(NotFoundError): ...
|
class UserNotFoundError(NotFoundError): ...
|
||||||
@@ -94,27 +95,41 @@ def add_user(
|
|||||||
exc_cls=OrgMissingError,
|
exc_cls=OrgMissingError,
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(HTTPStatus.CREATED)
|
return JSONResponse(HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
def _create_user(user: User, org: Org) -> bool:
|
def _create_user(user: User, org: Org) -> bool:
|
||||||
now_ = now()
|
now_ = now()
|
||||||
|
user_id = uuid4()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with dyn.transact_writer() as transact:
|
with dyn.transact_writer() as transact:
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
**user.model_dump(),
|
**user.model_dump(),
|
||||||
|
'id': user_id,
|
||||||
'sk': '0',
|
'sk': '0',
|
||||||
|
'email_verified': False,
|
||||||
'org_id': {org.id},
|
'org_id': {org.id},
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
transact.put(
|
||||||
|
item={
|
||||||
|
'id': user_id,
|
||||||
|
# Post-migration: rename `emails` to `EMAIL`
|
||||||
|
'sk': f'emails#{user.email}',
|
||||||
|
'email_verified': False,
|
||||||
|
'email_primary': True,
|
||||||
|
'created_at': now_,
|
||||||
|
}
|
||||||
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
# Post-migration: rename `cpf` to `CPF`
|
# Post-migration: rename `cpf` to `CPF`
|
||||||
'id': 'cpf',
|
'id': 'cpf',
|
||||||
'sk': user.cpf,
|
'sk': user.cpf,
|
||||||
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
exc_cls=CPFConflictError,
|
exc_cls=CPFConflictError,
|
||||||
@@ -124,13 +139,14 @@ def _create_user(user: User, org: Org) -> bool:
|
|||||||
# Post-migration: rename `email` to `EMAIL`
|
# Post-migration: rename `email` to `EMAIL`
|
||||||
'id': 'email',
|
'id': 'email',
|
||||||
'sk': user.email,
|
'sk': user.email,
|
||||||
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
exc_cls=EmailConflictError,
|
exc_cls=EmailConflictError,
|
||||||
)
|
)
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': user.id,
|
'id': user_id,
|
||||||
# Post-migration: rename `orgs` to `ORG`
|
# Post-migration: rename `orgs` to `ORG`
|
||||||
'sk': f'orgs#{org.id}',
|
'sk': f'orgs#{org.id}',
|
||||||
'name': org.name,
|
'name': org.name,
|
||||||
@@ -142,7 +158,7 @@ def _create_user(user: User, org: Org) -> bool:
|
|||||||
item={
|
item={
|
||||||
# Post-migration: rename `orgmembers` to `ORGMEMBER`
|
# Post-migration: rename `orgmembers` to `ORGMEMBER`
|
||||||
'id': f'orgmembers#{org.id}',
|
'id': f'orgmembers#{org.id}',
|
||||||
'sk': user.id,
|
'sk': user_id,
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
from http import HTTPMethod, HTTPStatus
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
PartitionKey,
|
||||||
|
SortKey,
|
||||||
|
TransactKey,
|
||||||
|
)
|
||||||
|
|
||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
|
|
||||||
@@ -40,6 +47,93 @@ def test_get_orgs(
|
|||||||
|
|
||||||
|
|
||||||
def test_add_user(
|
def test_add_user(
|
||||||
|
app,
|
||||||
|
seeds,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/users',
|
||||||
|
method=HTTPMethod.POST,
|
||||||
|
body={
|
||||||
|
'user': {
|
||||||
|
'name': 'Scott Weiland',
|
||||||
|
'email': 'scott@stonetemplopilots.com',
|
||||||
|
'cpf': '40245650016',
|
||||||
|
},
|
||||||
|
'org': {
|
||||||
|
'id': 'f6000f79-6e5c-49a0-952f-3bda330ef278',
|
||||||
|
'name': 'Branco do Brasil',
|
||||||
|
'cnpj': '00000000000191',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
assert r['statusCode'] == HTTPStatus.CREATED
|
||||||
|
r = dynamodb_persistence_layer.collection.query(
|
||||||
|
PartitionKey('orgmembers#f6000f79-6e5c-49a0-952f-3bda330ef278')
|
||||||
|
)
|
||||||
|
user_id = r['items'][0]['sk']
|
||||||
|
user = dynamodb_persistence_layer.collection.get_items(
|
||||||
|
TransactKey(user_id)
|
||||||
|
+ SortKey('0')
|
||||||
|
+ SortKey('emails#scott@stonetemplopilots.com')
|
||||||
|
)
|
||||||
|
assert user['name'] == 'Scott Weiland'
|
||||||
|
assert 'email' in user
|
||||||
|
assert 'email_verified' in user
|
||||||
|
assert 'created_at' in user
|
||||||
|
assert 'org_id' in user
|
||||||
|
assert 'emails#scott@stonetemplopilots.com' in user
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_exists(
|
||||||
|
app,
|
||||||
|
seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/users',
|
||||||
|
method=HTTPMethod.POST,
|
||||||
|
body={
|
||||||
|
'user': {
|
||||||
|
'name': 'Sérgio R Siqueira',
|
||||||
|
'email': 'sergio@somosbeta.com.br',
|
||||||
|
'cpf': '07879819908',
|
||||||
|
},
|
||||||
|
'org': {
|
||||||
|
'id': '2a8963fc-4694-4fe2-953a-316d1b10f1f5',
|
||||||
|
'name': 'pytest',
|
||||||
|
'cnpj': '04978826000180',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
assert r['statusCode'] == HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
r = dynamodb_persistence_layer.collection.query(
|
||||||
|
PartitionKey('orgmembers#2a8963fc-4694-4fe2-953a-316d1b10f1f5')
|
||||||
|
)
|
||||||
|
user_id = r['items'][0]['sk']
|
||||||
|
|
||||||
|
user = dynamodb_persistence_layer.collection.get_items(
|
||||||
|
TransactKey(user_id)
|
||||||
|
+ SortKey('0')
|
||||||
|
+ SortKey('emails#sergio@somosbeta.com.br', rename_key='email')
|
||||||
|
+ SortKey('orgs#2a8963fc-4694-4fe2-953a-316d1b10f1f5', rename_key='org')
|
||||||
|
)
|
||||||
|
assert 'mx_record_exists' in user['email']
|
||||||
|
assert 'cnpj' in user['org']
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_conflict(
|
||||||
app,
|
app,
|
||||||
seeds,
|
seeds,
|
||||||
http_api_proxy: HttpApiProxy,
|
http_api_proxy: HttpApiProxy,
|
||||||
@@ -57,14 +151,14 @@ def test_add_user(
|
|||||||
},
|
},
|
||||||
'org': {
|
'org': {
|
||||||
'id': 'f6000f79-6e5c-49a0-952f-3bda330ef278',
|
'id': 'f6000f79-6e5c-49a0-952f-3bda330ef278',
|
||||||
'name': 'Branco do Brasil',
|
'name': 'Banco do Brasil',
|
||||||
'cnpj': '00000000000191',
|
'cnpj': '00000000000191',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
lambda_context,
|
lambda_context,
|
||||||
)
|
)
|
||||||
assert r['statusCode'] == HTTPStatus.CREATED
|
assert r['statusCode'] == HTTPStatus.CONFLICT
|
||||||
|
|
||||||
|
|
||||||
def test_org_not_found(
|
def test_org_not_found(
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// Users
|
// Users
|
||||||
{"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", "cpf": "07879819908"}
|
||||||
|
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "emails#sergio@somosbeta.com.br", "email_primary": true, "mx_record_exists": true}
|
||||||
// User emails
|
{"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": "emails#sergio@somosbeta.com.br"}
|
|
||||||
|
|
||||||
// User orgs
|
// User orgs
|
||||||
{"id": "213a6682-2c59-4404-9189-12eec0a846d4", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "Banco do Brasil", "cnpj": "00000000000191"}
|
{"id": "15bacf02-1535-4bee-9022-19d106fd7518", "sk": "orgs#286f7729-7765-482a-880a-0b153ea799be", "name": "Banco do Brasil", "cnpj": "00000000000191"}
|
||||||
|
|
||||||
// Enrollments
|
// Enrollments
|
||||||
{"id": "578ec87f-94c7-4840-8780-bb4839cc7e64", "sk": "0", "course": {"id": "af3258f0-bccf-4781-aec6-d4c618d234a7", "name": "pytest", "access_period": 180}, "user": {"id": "068b4600-cc36-4b55-b832-bb620021705a", "name": "Benjamin Burnley", "email": "burnley@breakingbenjamin.com"}}
|
{"id": "578ec87f-94c7-4840-8780-bb4839cc7e64", "sk": "0", "course": {"id": "af3258f0-bccf-4781-aec6-d4c618d234a7", "name": "pytest", "access_period": 180}, "user": {"id": "068b4600-cc36-4b55-b832-bb620021705a", "name": "Benjamin Burnley", "email": "burnley@breakingbenjamin.com"}}
|
||||||
@@ -14,9 +13,15 @@
|
|||||||
{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest", "cnpj": "04978826000180"}
|
{"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest", "cnpj": "04978826000180"}
|
||||||
{"id": "f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "0", "name": "Banco do Brasil", "cnpj": "00000000000191"}
|
{"id": "f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "0", "name": "Banco do Brasil", "cnpj": "00000000000191"}
|
||||||
|
|
||||||
|
{"id": "orgmembers#f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "15bacf02-1535-4bee-9022-19d106fd7518"}
|
||||||
|
|
||||||
|
// Indicies
|
||||||
// CNPJs
|
// CNPJs
|
||||||
{"id": "cnpj", "sk": "04978826000180", "org_id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5"}
|
{"id": "cnpj", "sk": "04978826000180", "org_id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5"}
|
||||||
{"id": "cnpj", "sk": "00000000000191", "org_id": "6000f79-6e5c-49a0-952f-3bda330ef278"}
|
{"id": "cnpj", "sk": "00000000000191", "org_id": "6000f79-6e5c-49a0-952f-3bda330ef278"}
|
||||||
|
|
||||||
// CPFs
|
// CPFs
|
||||||
{"id": "cpf", "sk": "07879819908", "user_id": "15bacf02-1535-4bee-9022-19d106fd7518""}
|
{"id": "cpf", "sk": "07879819908", "user_id": "15bacf02-1535-4bee-9022-19d106fd7518"}
|
||||||
|
|
||||||
|
// Emails
|
||||||
|
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "15bacf02-1535-4bee-9022-19d106fd7518"}
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
BookCopyIcon,
|
BookCopyIcon,
|
||||||
CalendarClockIcon,
|
CalendarClockIcon,
|
||||||
DollarSign,
|
DollarSign,
|
||||||
|
FileBadgeIcon,
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ShieldUserIcon,
|
ShieldUserIcon,
|
||||||
@@ -53,6 +54,11 @@ const data = {
|
|||||||
url: '/enrollments',
|
url: '/enrollments',
|
||||||
icon: GraduationCap
|
icon: GraduationCap
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Certificações',
|
||||||
|
url: '/certs',
|
||||||
|
icon: FileBadgeIcon
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Agendamentos',
|
title: 'Agendamentos',
|
||||||
url: '/scheduled',
|
url: '/scheduled',
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ export type User = {
|
|||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ to: '', title: 'Perfil', end: true },
|
{ to: '', title: 'Perfil', end: true },
|
||||||
{ to: 'emails', title: 'Emails' },
|
{ to: 'emails', title: 'Emails' }
|
||||||
{ to: 'orgs', title: 'Empresas' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function Route({
|
export default function Route({
|
||||||
|
|||||||
@@ -79,16 +79,16 @@ export default function Route({ loaderData: { data } }) {
|
|||||||
defaultValue={searchParams.get('q') || ''}
|
defaultValue={searchParams.get('q') || ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setSearchParams((searchParams) => {
|
setSearchParams((searchParams) => {
|
||||||
searchParams.set('q', value)
|
searchParams.set('q', String(value))
|
||||||
searchParams.delete('p')
|
searchParams.delete('p')
|
||||||
return searchParams
|
return searchParams
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" asChild>
|
<Button asChild>
|
||||||
<Link to="add">
|
<Link to="add">
|
||||||
<PlusIcon /> Adicionar colaborador
|
<PlusIcon /> Adicionar
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user