From 11d66a85d3fc6602fc163c26e5d6fd1017cec47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Tue, 2 Dec 2025 22:54:47 -0300 Subject: [PATCH] add never logged to add user --- api.saladeaula.digital/app/api_gateway.py | 4 +- api.saladeaula.digital/app/config.py | 2 + .../app/routes/orgs/users/__init__.py | 46 ++++++++++------- .../app/routes/users/__init__.py | 1 - .../app/routes/users/emails.py | 20 ++++---- .../tests/routes/orgs/test_users.py | 2 +- .../tests/routes/users/test_emails.py | 50 +++++++++---------- 7 files changed, 68 insertions(+), 57 deletions(-) diff --git a/api.saladeaula.digital/app/api_gateway.py b/api.saladeaula.digital/app/api_gateway.py index 255bc2f..cc868b9 100644 --- a/api.saladeaula.digital/app/api_gateway.py +++ b/api.saladeaula.digital/app/api_gateway.py @@ -1,7 +1,7 @@ from typing import Generic, Mapping -from aws_lambda_powertools.event_handler import content_types -from aws_lambda_powertools.event_handler.api_gateway import Response, ResponseT +from aws_lambda_powertools.event_handler import Response, content_types +from aws_lambda_powertools.event_handler.api_gateway import ResponseT class JSONResponse(Response, Generic[ResponseT]): diff --git a/api.saladeaula.digital/app/config.py b/api.saladeaula.digital/app/config.py index df027d9..891f83a 100644 --- a/api.saladeaula.digital/app/config.py +++ b/api.saladeaula.digital/app/config.py @@ -8,3 +8,5 @@ COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore PAPERFORGE_API = 'https://paperforge.saladeaula.digital' + +INTERNAL_EMAIL_DOMAIN = 'users.noreply.saladeaula.digital' diff --git a/api.saladeaula.digital/app/routes/orgs/users/__init__.py b/api.saladeaula.digital/app/routes/orgs/users/__init__.py index 92d3600..340579a 100644 --- a/api.saladeaula.digital/app/routes/orgs/users/__init__.py +++ b/api.saladeaula.digital/app/routes/orgs/users/__init__.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, EmailStr, Field from api_gateway import JSONResponse from boto3clients import dynamodb_client -from config import USER_TABLE +from config import INTERNAL_EMAIL_DOMAIN, USER_TABLE router = Router() dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @@ -33,21 +33,24 @@ class User(BaseModel): email: EmailStr -class CPFConflictError(Exception): ... - - -class EmailConflictError(Exception): ... - - -class UserConflictError(ServiceError): +class ConflictError(ServiceError): def __init__(self, msg: str | dict): super().__init__(HTTPStatus.CONFLICT, msg) -class UserMissingError(NotFoundError): ... +class CPFConflictError(ConflictError): ... -class OrgMissingError(NotFoundError): ... +class EmailConflictError(ConflictError): ... + + +class UserConflictError(ConflictError): ... + + +class UserNotFoundError(NotFoundError): ... + + +class OrgNotFoundError(NotFoundError): ... @router.post('//users') @@ -103,13 +106,13 @@ def unlink(org_id: str, user_id: str): def _create_user(user: User, org: Org) -> bool: now_ = now() user_id = uuid4() - email_verified = '@users.noreply.saladeaula.digital' in user.email + email_verified = INTERNAL_EMAIL_DOMAIN in user.email try: with dyn.transact_writer() as transact: transact.put( - item={ - **user.model_dump(), + item=user.model_dump() + | { 'id': user_id, 'sk': '0', 'email_verified': email_verified, @@ -119,6 +122,13 @@ def _create_user(user: User, org: Org) -> bool: 'created_at': now_, }, ) + transact.put( + item={ + 'id': user_id, + 'sk': 'NEVER_LOGGED', + 'created_at': now_, + } + ) transact.put( item={ 'id': user_id, @@ -188,7 +198,7 @@ def _create_user(user: User, org: Org) -> bool: transact.condition( key=KeyPair(org.id, '0'), # type: ignore cond_expr='attribute_exists(sk)', - exc_cls=OrgMissingError, + exc_cls=OrgNotFoundError, ) except (CPFConflictError, EmailConflictError): return False @@ -202,14 +212,14 @@ def _add_member(user_id: str, org: Org) -> None: with dyn.transact_writer() as transact: transact.update( key=KeyPair(user_id, '0'), - update_expr='ADD tenant_id :org_id', # Post-migration: uncomment the following line # update_expr='ADD tenant_id :org_id', + update_expr='ADD tenant_id :org_id', expr_attr_values={ ':org_id': {org.id}, }, cond_expr='attribute_exists(sk)', - exc_cls=UserMissingError, + exc_cls=UserNotFoundError, ) transact.put( item={ @@ -234,7 +244,7 @@ def _add_member(user_id: str, org: Org) -> None: transact.condition( key=KeyPair(org.id, '0'), # type: ignore cond_expr='attribute_exists(sk)', - exc_cls=OrgMissingError, + exc_cls=OrgNotFoundError, ) @@ -254,6 +264,6 @@ def _get_user_id(user: User) -> str: ).get('id') if not user_id: - raise UserMissingError() + raise UserNotFoundError('User not found') return user_id diff --git a/api.saladeaula.digital/app/routes/users/__init__.py b/api.saladeaula.digital/app/routes/users/__init__.py index 7d1b628..dfe4a0a 100644 --- a/api.saladeaula.digital/app/routes/users/__init__.py +++ b/api.saladeaula.digital/app/routes/users/__init__.py @@ -92,7 +92,6 @@ def update( cond_expr='attribute_not_exists(sk)', exc_cls=RateLimitExceededError, ) - transact.update( key=KeyPair(user_id, '0'), update_expr='SET #name = :name, cpf = :cpf, updated_at = :now', diff --git a/api.saladeaula.digital/app/routes/users/emails.py b/api.saladeaula.digital/app/routes/users/emails.py index 4df6639..0302600 100644 --- a/api.saladeaula.digital/app/routes/users/emails.py +++ b/api.saladeaula.digital/app/routes/users/emails.py @@ -73,6 +73,16 @@ def add( 'created_at': now_, } ) + transact.put( + item={ + 'id': user_id, + 'sk': f'EMAIL_VERIFICATION#{uuid4()}', + 'name': name, + 'email': email, + 'ttl': ttl(start_dt=now_, days=30), + 'created_at': now_, + } + ) transact.put( item={ # Post-migration (users): rename `email` to `EMAIL` @@ -84,16 +94,6 @@ def add( cond_expr='attribute_not_exists(sk)', exc_cls=EmailConflictError, ) - transact.put( - item={ - 'id': user_id, - 'sk': f'EMAIL_VERIFICATION#{uuid4()}', - 'name': name, - 'email': email, - 'ttl': ttl(start_dt=now_, days=30), - 'created_at': now_, - } - ) return JSONResponse(status_code=HTTPStatus.CREATED) diff --git a/api.saladeaula.digital/tests/routes/orgs/test_users.py b/api.saladeaula.digital/tests/routes/orgs/test_users.py index 3ba4310..62a6f92 100644 --- a/api.saladeaula.digital/tests/routes/orgs/test_users.py +++ b/api.saladeaula.digital/tests/routes/orgs/test_users.py @@ -150,7 +150,7 @@ def test_org_not_found( lambda_context, ) body = json.loads(r['body']) - assert body['type'] == 'OrgMissingError' + assert body['type'] == 'OrgNotFoundError' def test_unlink( diff --git a/api.saladeaula.digital/tests/routes/users/test_emails.py b/api.saladeaula.digital/tests/routes/users/test_emails.py index b18f289..9f81e63 100644 --- a/api.saladeaula.digital/tests/routes/users/test_emails.py +++ b/api.saladeaula.digital/tests/routes/users/test_emails.py @@ -14,15 +14,15 @@ def test_get_emails( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails", method=HTTPMethod.GET, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.OK + assert r["statusCode"] == HTTPStatus.OK - body = json.loads(r['body']) - assert len(body['items']) == 2 + body = json.loads(r["body"]) + assert len(body["items"]) == 2 def test_add_email( @@ -34,26 +34,26 @@ def test_add_email( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails", method=HTTPMethod.POST, body={ - 'email': 'osergiosiqueira+pytest@gmail.com', + "email": "osergiosiqueira+pytest@gmail.com", }, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.CREATED + assert r["statusCode"] == HTTPStatus.CREATED r = dynamodb_persistence_layer.collection.query( KeyPair( - '15bacf02-1535-4bee-9022-19d106fd7518', - 'EMAIL_VERIFICATION', + "15bacf02-1535-4bee-9022-19d106fd7518", + "EMAIL_VERIFICATION", ) ) - items = r['items'] + items = r["items"] assert len(items) == 2 - assert any(x.get('email') == 'osergiosiqueira+pytest@gmail.com' for x in items) + assert any(x.get("email") == "osergiosiqueira+pytest@gmail.com" for x in items) def test_email_as_primary( @@ -65,24 +65,24 @@ def test_email_as_primary( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/primary', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/primary", method=HTTPMethod.PATCH, body={ - 'old_email': 'sergio@somosbeta.com.br', - 'new_email': 'osergiosiqueira@gmail.com', - 'email_verified': 'false', + "old_email": "sergio@somosbeta.com.br", + "new_email": "osergiosiqueira@gmail.com", + "email_verified": "false", }, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.NO_CONTENT + assert r["statusCode"] == HTTPStatus.NO_CONTENT r = dynamodb_persistence_layer.collection.get_item( - KeyPair('15bacf02-1535-4bee-9022-19d106fd7518', '0') + KeyPair("15bacf02-1535-4bee-9022-19d106fd7518", "0") ) - assert r['email'] == 'osergiosiqueira@gmail.com' - assert r['emails'] == {'osergiosiqueira@gmail.com', 'sergio@somosbeta.combr'} + assert r["email"] == "osergiosiqueira@gmail.com" + assert r["emails"] == {"osergiosiqueira@gmail.com", "sergio@somosbeta.combr"} def test_verify_email( @@ -93,13 +93,13 @@ def test_verify_email( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/0d29c753-55f8-42d2-908b-e4976aafc183/verify', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/0d29c753-55f8-42d2-908b-e4976aafc183/verify", method=HTTPMethod.POST, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.NO_CONTENT + assert r["statusCode"] == HTTPStatus.OK def test_verify_email_notfound( @@ -110,13 +110,13 @@ def test_verify_email_notfound( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/abc/verify', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/abc/verify", method=HTTPMethod.POST, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.NOT_FOUND + assert r["statusCode"] == HTTPStatus.NOT_FOUND def test_remove_emal( @@ -127,10 +127,10 @@ def test_remove_emal( ): r = app.lambda_handler( http_api_proxy( - raw_path='/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/osergiosiqueira@gmail.com', + raw_path="/users/15bacf02-1535-4bee-9022-19d106fd7518/emails/osergiosiqueira@gmail.com", method=HTTPMethod.DELETE, ), lambda_context, ) - assert r['statusCode'] == HTTPStatus.NO_CONTENT + assert r["statusCode"] == HTTPStatus.NO_CONTENT