add session route

This commit is contained in:
2025-08-16 23:53:09 -03:00
parent a53f37393a
commit 21f6eb030f
21 changed files with 311 additions and 599 deletions

View File

@@ -24,8 +24,6 @@ router = Router()
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
user_collect = DynamoDBCollection(user_layer)
enrollment_collect = DynamoDBCollection(enrollment_layer)
processor = BatchProcessor()
@@ -54,7 +52,7 @@ class Payload(BaseModel):
compress=True,
tags=['Enrollment'],
middlewares=[
TenantMiddleware(user_collect),
TenantMiddleware(user_layer.collection),
],
)
def enroll_(payload: Payload):

View File

@@ -15,7 +15,7 @@ JSONL_FILES = (
# 'test-orders.jsonl',
'test-users.jsonl',
# 'test-enrollments.jsonl',
# 'test-courses.jsonl',
'test-courses.jsonl',
)

View File

@@ -1,64 +1,67 @@
{"id": {"S": "439e9a43-ab92-469a-a849-b6e824370f80"}, "access_period": {"S": "360"}, "name": {"S": "No\u00e7\u00f5es em Primeiros Socorros"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:06:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "2c1e724a-58c6-4c20-90df-18b5660d6304"}, "metadata__unit_price": {"N": "199"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "94"}}
{"id": {"S": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-10 SEP 08 horas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:02:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "1d86444e-36d6-4ed7-8cea-24a4df9ca15f"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "276"}}
{"id": {"S": "4ea2498a-a6a9-4293-94d0-ceeb248e64b7"}, "access_period": {"S": "720"}, "name": {"S": "NR-10 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:07:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "38"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "21"}}
{"id": {"S": "e1c44881-2fe3-484e-ada2-12b6bf5b9398"}, "access_period": {"S": "720"}, "name": {"S": "NR-35 Seguran\u00e7a nos Trabalhos em Altura (Te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:11:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "42"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "1"}}
{"id": {"S": "281198c2-f293-4acc-b96e-e4a2d5f6b73c"}, "access_period": {"S": "360"}, "name": {"S": "CIPA"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:10:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "41"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "3"}}
{"id": {"S": "4866c068-577a-45b0-b41a-41a7dc6b9ab7"}, "access_period": {"S": "360"}, "name": {"S": "Combate a Inc\u00eandio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:17:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "53"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "18"}}
{"id": {"S": "f10c3283-7722-41c6-ba5d-222f9f4f48af"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Operador de Empilhadeira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:13:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "49"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "23"}}
{"id": {"S": "39f89a69-3d94-4dd6-9049-66e540fb2f32"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-10 Complementar (SEP)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:20:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "36"}}
{"id": {"S": "38d8ba20-49a4-4c69-b674-b70a985eb76a"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 4"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:21:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56771ce5-4680-4ce4-b257-8f2362975494"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "218"}}
{"id": {"S": "d800d2a9-ae76-46de-be82-3e06ae6afcee"}, "access_period": {"S": "720"}, "name": {"S": "NR-20 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:29:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "70"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "28"}}
{"id": {"S": "3c27ea9c-9464-46a1-9717-8c1441793186"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 1"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:28:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "6f6fbc20-57f1-4d68-bf00-00119141894f"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "200"}}
{"id": {"S": "96c2553a-d087-42ad-be5e-e960ea673c3d"}, "access_period": {"S": "360"}, "name": {"S": "NR-18 Sinaleiro e Amarrador de Cargas para I\u00e7amento"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:30:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "711bad57-2a03-43ff-91d0-31d151063897"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "270"}}
{"id": {"S": "5c119d4b-573c-4d8d-a99d-63756af2f4c5"}, "access_period": {"S": "360"}, "name": {"S": "NR-06 - Equipamento de Prote\u00e7\u00e3o Individual - EPI"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:32:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "78"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "34"}}
{"id": {"S": "7f7905aa-ec6d-4189-b884-50fa9b1bd0b8"}, "access_period": {"S": "360"}, "name": {"S": "NR-10 Reciclagem: 08 horas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:01:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "005f262d-9eda-4304-8639-31a86efb3086"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "275"}}
{"id": {"S": "07da69f2-2a2c-4771-b766-633295476ad7"}, "access_period": {"S": "360"}, "name": {"S": "NR-26 Sinaliza\u00e7\u00e3o de Seguran\u00e7a"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:05:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "29b46896-eb93-40ab-8439-25d5129d108a"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "123"}}
{"id": {"S": "d6520884-89e7-4843-b77f-7777c5376d50"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-13 Operador de Caldeiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:03:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "1eac1304-6b5a-4fe1-884f-ef2dec753313"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "261"}}
{"id": {"S": "9301601e-385a-4525-a65a-4054f669632f"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-13 Vasos de Press\u00e3o e Unidades de Processo"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:12:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "452f8158-4ed9-4ca5-a53f-c82db430e990"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "262"}}
{"id": {"S": "52b4a909-b6a9-456e-a7b9-c0b3c18ebe00"}, "access_period": {"S": "360"}, "name": {"S": "NR-12 M\u00e1quinas e Equipamentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:25:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "62"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "24"}}
{"id": {"S": "b23493dd-6359-4352-97be-12dca3a21ca6"}, "access_period": {"S": "360"}, "name": {"S": "NR-33 Trabalhadores Autorizados e Vigias em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:18:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "54"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "32"}}
{"id": {"S": "5c53656d-9557-4ef9-8e05-08d3190bb115"}, "access_period": {"S": "360"}, "name": {"S": "NR-13 Operador de Caldeiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:26:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "63"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "25"}}
{"id": {"S": "124ca098-b609-4550-a83c-6b9120a2db42"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 Transpaleteiras"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:34:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7d6e413e-800b-48b6-9b60-f8ac5d3ee730"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "265"}}
{"id": {"S": "6dd8f711-5c5a-477a-971f-122cbac4ce48"}, "access_period": {"S": "720"}, "name": {"S": "NR-35 Supervisor de Trabalho em Altura"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:27:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "64"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "33"}}
{"id": {"S": "4e52d4e9-0566-4f8c-8307-1db770e4c33c"}, "access_period": {"S": "360"}, "name": {"S": "NR-17 Ergonomia"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:31:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "723534ae-36ae-4253-bb73-966c8268779d"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "105"}}
{"id": {"S": "863214e8-26e2-440b-854b-a0ced0164bbf"}, "access_period": {"S": "360"}, "name": {"S": "CIPA Grau de Risco 3 (te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:09:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "4079b429-aac4-4a41-937a-38bd6101d875"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "259"}}
{"id": {"S": "7aba7598-83b2-4df7-938c-d075ffef47ca"}, "access_period": {"S": "360"}, "name": {"S": "NR-18 - Constru\u00e7\u00e3o Civil"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:16:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "52"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "27"}}
{"id": {"S": "2e1c93c2-1779-482b-9552-c04e09db8349"}, "access_period": {"S": "720"}, "name": {"S": "NR-10 Complementar (SEP)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:19:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "55"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "22"}}
{"id": {"S": "70827c13-1db5-4499-977f-9a6623e45161"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Rebocadores"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:22:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "56d1c710-36b1-4db5-8a7a-dacb7098dbad"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "154"}}
{"id": {"S": "4a0c4652-fcb3-4362-8fff-bc5ac3ba1cf3"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 Plataforma de Trabalho Elevat\u00f3ria (PTA)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:04:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "2706ca6d-2b1b-47aa-8f02-5d39c8833b9d"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "166"}}
{"id": {"S": "c01ec8a2-0359-4351-befb-76c3577339e0"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-10 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:08:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "40"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "35"}}
{"id": {"S": "a1a8727c-0519-4692-93e7-81dbe66e167f"}, "access_period": {"S": "360"}, "name": {"S": "Dire\u00e7\u00e3o Defensiva (20 horas)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:15:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "50"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "19"}}
{"id": {"S": "eb19c520-5546-4c57-898d-029c86e59fb6"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-33 Supervisores em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:14:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "4ea8aaec-cfd3-4ec1-a15b-a72fac56b371"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "117"}}
{"id": {"S": "386f7086-2871-436f-85f5-31d632fbf624"}, "access_period": {"S": "360"}, "name": {"S": "Boas Pr\u00e1ticas em Manipula\u00e7\u00e3o de Alimentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:24:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "59"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "4"}}
{"id": {"S": "3f284753-85ce-4f53-8de7-cdfcdaf9515b"}, "access_period": {"S": "360"}, "name": {"S": "NR-33 Supervisor em Espa\u00e7o Confinado"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:23:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "57"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "31"}}
{"id": {"S": "00ebdd8d-b4db-4437-8814-274811a4c469"}, "access_period": {"S": "360"}, "name": {"S": "Dire\u00e7\u00e3o Defensiva (08 horas)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:33:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7ac2e34e-232a-427c-a3fc-32198e3a51c6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "194"}}
{"id": {"S": "8efe00d2-38e2-4281-8f5e-b9113e91374b"}, "access_period": {"S": "1080"}, "name": {"S": "Reciclagem em NR-20 B\u00e1sico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:43:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "91"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "39"}}
{"id": {"S": "a3c46d94-cf31-4b5f-8de3-6aa1c2d423f0"}, "access_period": {"S": "360"}, "name": {"S": "NR-17 Ergonomia para Teleatendimento/Telemarketing"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:48:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "94dc4c63-9f23-4101-a0d7-d42bfa527cbc"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "269"}}
{"id": {"S": "450a70ca-8ab5-4520-8a22-0e277359797d"}, "access_period": {"S": "365"}, "name": {"S": "NR-18 PEMT PTA"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:52:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "184"}}
{"id": {"S": "f05293f0-2ff4-4026-9e65-2f0f67d9f83b"}, "access_period": {"S": "360"}, "name": {"S": "Preven\u00e7\u00e3o e combate ao ass\u00e9dio sexual e \u00e0s demais formas de viol\u00eancia no trabalho"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:00:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "da03eac1-e328-49d0-8014-5cdd023cb543"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "260"}}
{"id": {"S": "446426ce-c0f0-4238-83ed-95e8c0434f45"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Rebocadores"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:04:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "f78d2241-13fd-45ab-81cc-0acb781b4107"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "189"}}
{"id": {"S": "479516a7-5431-452e-8f28-228e34b86e0c"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a em Transpaleteira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:03:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "e39a8718-cf10-4dcc-a152-8c50b4b63e14"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "268"}}
{"id": {"S": "95a1fcb9-ba16-4b3c-a59d-047ca32078ff"}, "access_period": {"S": "360"}, "name": {"S": "NR-20 Inicia\u00e7\u00e3o"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:36:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "83"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "29"}}
{"id": {"S": "2b9a6e19-2e2d-4fc2-8924-b45d47057715"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-33 Trabalhos em Espa\u00e7os Confinados"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:42:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "90"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "38"}}
{"id": {"S": "76a5ba94-e11c-48f5-88eb-9326df9be264"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-12 M\u00e1quinas e Equipamentos"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:45:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "929960de-a62d-4669-91e5-8fef8d670103"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "273"}}
{"id": {"S": "0707270e-623f-486a-8dbd-d852377a208c"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-20 - Intermedi\u00e1rio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:44:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "92"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "57"}}
{"id": {"S": "96c03c32-089c-4ccb-8aa1-73b0f49228b9"}, "access_period": {"S": "360"}, "name": {"S": "Lei Lucas: Primeiros Socorros nas Escolas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:49:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "9bb3fe7d-e29d-4a2f-91d9-87910152152b"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "278"}}
{"id": {"S": "6a403773-aeac-4e6a-ac39-dc958e4be52a"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-11 - Operador de Empilhadeira"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:47:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "94"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "63"}}
{"id": {"S": "0e39c7af-a812-49aa-ac12-72f4e0ede8c9"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem de NR-18 Plataforma de Trabalho A\u00e9reo PEMT"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:51:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a52eb5c5-4a5c-404b-96fe-e34037805d1e"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "272"}}
{"id": {"S": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 2"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:55:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b4de57c8-59e8-43ad-a6fa-6735d4faa54b"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "206"}}
{"id": {"S": "801d1115-b4e8-4213-96b1-0b4f99bf202e"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-18 B\u00e1sico em seguran\u00e7a do trabalho"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:35:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "7e981a6b-8776-4747-bc3b-dcad638b5273"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "266"}}
{"id": {"S": "6689a04a-99c1-4150-b1ed-c131b6dc5bb5"}, "access_period": {"S": "720"}, "name": {"S": "NR-20 Intermedi\u00e1rio"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:37:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "84"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "30"}}
{"id": {"S": "c19cd7ee-3cc8-4f9c-95ff-dad7993f49b1"}, "access_period": {"S": "360"}, "name": {"S": "Gest\u00e3o da Cultura de Seguran\u00e7a"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:40:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "87"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "17"}}
{"id": {"S": "d9f9c3d6-ba97-4695-b1fb-e2539158b064"}, "access_period": {"S": "360"}, "name": {"S": "Reciclagem em NR-20 Avan\u00e7ado II"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:50:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "a3dd23e1-09a0-4c24-a042-08ed65e07fc1"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "131"}}
{"id": {"S": "b945be62-408d-4099-a75c-4d1dda929659"}, "access_period": {"S": "360"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Pontes Rolantes"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:56:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "bf8b81d6-f83a-4216-bcdb-31ba1e21ffcb"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "148"}}
{"id": {"S": "5c9c1ff1-361f-479d-bd2c-eb3b124a74fc"}, "access_period": {"S": "360"}, "name": {"S": "NR-31 CIPATR"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:54:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b331f93b-9fc5-4791-bd81-fb10c8f5e401"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "137"}}
{"id": {"S": "80dfc302-4e05-4f23-944d-9a2768cb6c7d"}, "access_period": {"S": "360"}, "name": {"S": "LOTO Lockout e Tagout"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:58:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "d23d569e-51e8-499a-b407-bd782c64a6ac"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "271"}}
{"id": {"S": "c2d1362f-aa7f-40b0-bd15-37570bda5f25"}, "access_period": {"S": "360"}, "name": {"S": "PCA - Programa de Conserva\u00e7\u00e3o Auditiva"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:05:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "fa00bc23-d6b8-4dc5-86e7-dc0a9bdb2306"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "247"}}
{"id": {"S": "4682187a-cb5c-47a8-9597-ad9243a6d717"}, "access_period": {"S": "365"}, "name": {"S": "NR-11 Seguran\u00e7a na Opera\u00e7\u00e3o de Talhas"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:02:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "e2e25a9a-3104-47a7-90da-0be05a157d21"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "144"}}
{"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"}, "access_period": {"N": "360"}, "name": {"S": "NR-11 \u2013 Transporte, movimenta\u00e7\u00e3o, armazenagem e manuseio de materiais"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T01:01:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "dc1a0428-47bf-4db1-a5da-24be49c9fda6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "100"}, "cert": {"NULL": true}, "update_date": {"S": "2025-04-04T20:30:05.033976-03:00"}}
{"id": {"S": "1c7b1cf0-6973-4271-9407-6e974f0094e9"}, "access_period": {"S": "360"}, "name": {"S": "PPR Programa de Prote\u00e7\u00e3o Respirat\u00f3ria"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:39:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "86d310e8-a87d-4b38-9e43-3dd247c6a522"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "224"}}
{"id": {"S": "6d17d9cf-96be-42a4-bae5-75926e1e832a"}, "access_period": {"S": "720"}, "name": {"S": "Exposi\u00e7\u00e3o ao Benzeno - (Portaria 1109)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:38:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "86"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "20"}}
{"id": {"S": "3b05b03c-8714-4f98-90e0-2a3ac4940035"}, "access_period": {"S": "720"}, "name": {"S": "NR-13 Vasos de Press\u00e3o e Unidades de Processo"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:41:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "89"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "26"}}
{"id": {"S": "30bb357f-2f48-4764-93d1-ffe219cbc5d3"}, "access_period": {"S": "720"}, "name": {"S": "Reciclagem em NR-35 Trabalhos em Altura ( Te\u00f3rico)"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:46:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "93"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "51"}}
{"id": {"S": "f7def039-bf27-496a-94ff-0667c9c0c0db"}, "access_period": {"S": "720"}, "name": {"S": "CIPA Grau de Risco 3"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:53:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "b09ca8da-f342-4a70-aaaa-67ac81569533"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "212"}}
{"id": {"S": "b3d0e345-a8e5-4dc2-a3b0-218bc894ee55"}, "access_period": {"S": "360"}, "name": {"S": "NR-10: No\u00e7\u00f5es do Risco El\u00e9trico"}, "sk": {"S": "0"}, "create_date": {"S": "2024-12-30T00:57:33.088916-03:00"}, "metadata__betaeducacao_id": {"S": "c5b683a3-f432-46e9-94e4-290a788d2ff6"}, "metadata__tenant_id": {"S": "*"}, "metadata__konviva_id": {"S": "274"}}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:01:33.088915-03:00", "metadata__konviva_class_id":100, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839", "name": "NR-11 Transporte, movimentação, armazenagem e manuseio de materiais", "metadata__unit_price":109}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:01:33.088916-03:00", "metadata__konviva_class_id":275, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "7f7905aa-ec6d-4189-b884-50fa9b1bd0b8", "name": "Reciclagem em NR-10 Básico (08 horas)", "metadata__unit_price":169}
{"updated_at": "2025-07-24T17:39:57.432609-03:00", "access_period": 360, "created_at": "2024-12-30T00:56:33.088916-03:00", "metadata__konviva_class_id":148, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "b945be62-408d-4099-a75c-4d1dda929659", "name": "NR-11 Pontes Rolantes"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:05:33.088916-03:00", "metadata__konviva_class_id":247, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "c2d1362f-aa7f-40b0-bd15-37570bda5f25", "name": "PCA Programa de Conservação Auditiva", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:12:33.088916-03:00", "metadata__konviva_class_id":262, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "9301601e-385a-4525-a65a-4054f669632f", "name": "Reciclagem em NR-13 Vasos de Pressão e Unidades de Processo", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:58:33.088916-03:00", "metadata__konviva_class_id":271, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "80dfc302-4e05-4f23-944d-9a2768cb6c7d", "name": "LOTO Lockout e Tagout", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:37:33.088916-03:00", "metadata__konviva_class_id":30, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "6689a04a-99c1-4150-b1ed-c131b6dc5bb5", "name": "NR-20 Intermediário", "metadata__unit_price":179}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:03:33.088916-03:00", "metadata__konviva_class_id":261, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "d6520884-89e7-4843-b77f-7777c5376d50", "name": "Reciclagem em NR-13 Operador de Caldeiras", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:50:33.088916-03:00", "metadata__konviva_class_id":131, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "d9f9c3d6-ba97-4695-b1fb-e2539158b064", "name": "Reciclagem em NR-20 Avançado II", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:34:33.088916-03:00", "metadata__konviva_class_id":265, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "124ca098-b609-4550-a83c-6b9120a2db42", "name": "Reciclagem em NR-11 Transpaleteiras", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:27:33.088916-03:00", "metadata__konviva_class_id":33, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "6dd8f711-5c5a-477a-971f-122cbac4ce48", "name": "NR-35 Supervisor de Trabalho em Altura", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T01:02:33.088916-03:00", "metadata__konviva_class_id":144, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "4682187a-cb5c-47a8-9597-ad9243a6d717", "name": "NR-11 Talhas", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:18:33.088916-03:00", "metadata__konviva_class_id":32, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "b23493dd-6359-4352-97be-12dca3a21ca6", "name": "NR-33 Trabalhadores Autorizados e Vigias em Espaço Confinado", "metadata__unit_price":179}
{"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:54:33.088916-03:00", "metadata__konviva_class_id":137, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "5c9c1ff1-361f-479d-bd2c-eb3b124a74fc", "name": "NR-31 CIPATR"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2025-07-09T17:31:26.764492-03:00", "metadata__konviva_class_id":148, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "53ef0924-3521-4186-a707-88fbfa137179", "name": "NR-11 Pontes Rolantes", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:31:33.088916-03:00", "metadata__konviva_class_id":105, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4e52d4e9-0566-4f8c-8307-1db770e4c33c", "name": "NR-17 Ergonomia", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:25:33.088916-03:00", "metadata__konviva_class_id":24, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "52b4a909-b6a9-456e-a7b9-c0b3c18ebe00", "name": "NR-12 Máquinas e Equipamentos", "metadata__unit_price":149}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:40:33.088916-03:00", "metadata__konviva_class_id":17, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "c19cd7ee-3cc8-4f9c-95ff-dad7993f49b1", "name": "Gestão da Cultura de Segurança", "metadata__unit_price":199}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:35:33.088916-03:00", "metadata__konviva_class_id":266, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "801d1115-b4e8-4213-96b1-0b4f99bf202e", "name": "Reciclagem em NR-18 Básico em segurança do trabalho", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:05:33.088916-03:00", "metadata__konviva_class_id":123, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "07da69f2-2a2c-4771-b766-633295476ad7", "name": "NR-26 Sinalização de Segurança", "metadata__unit_price":109}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:26:33.088916-03:00", "metadata__konviva_class_id":25, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "5c53656d-9557-4ef9-8e05-08d3190bb115", "name": "NR-13 Operador de Caldeiras", "metadata__unit_price":199}
{"access_period": 360, "created_at": "2025-07-14T15:09:18.559528-03:00", "metadata__konviva_class_id":281, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "a955518e-ebcb-4441-b914-ddc9ecef84f0", "name": "NR-11 Operador de Munck"}
{"updated_at": "2025-07-24T17:44:10.114431-03:00", "access_period": 360, "created_at": "2024-12-30T00:16:33.088916-03:00", "metadata__konviva_class_id":27, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "7aba7598-83b2-4df7-938c-d075ffef47ca", "name": "NR-18 Construção Civil (descontinuado)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:00:33.088916-03:00", "metadata__konviva_class_id":260, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "f05293f0-2ff4-4026-9e65-2f0f67d9f83b", "name": "Prevenção e combate ao assédio sexual e às demais formas de violência no trabalho", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:48:33.088916-03:00", "metadata__konviva_class_id":269, "tenant_id": "*", "cert": {"exp_interval": 180}, "sk": "0", "id": "a3c46d94-cf31-4b5f-8de3-6aa1c2d423f0", "name": "NR-17 Ergonomia para Teleatendimento/Telemarketing", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:04:33.088916-03:00", "metadata__konviva_class_id":189, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "446426ce-c0f0-4238-83ed-95e8c0434f45", "name": "Reciclagem em NR-11 Rebocadores", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:43:33.088916-03:00", "metadata__konviva_class_id":39, "tenant_id": "*", "cert": {"exp_interval": 1000}, "sk": "0", "id": "8efe00d2-38e2-4281-8f5e-b9113e91374b", "name": "Reciclagem em NR-20 Básico", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:22:33.088916-03:00", "metadata__konviva_class_id":154, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "70827c13-1db5-4499-977f-9a6623e45161", "name": "NR-11 Rebocadores", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T01:03:33.088916-03:00", "metadata__konviva_class_id":268, "tenant_id": "*", "cert": {"exp_interval": 365}, "sk": "0", "id": "479516a7-5431-452e-8f28-228e34b86e0c", "name": "NR-11 Transpaleteira", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:19:33.088916-03:00", "metadata__konviva_class_id":22, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "2e1c93c2-1779-482b-9552-c04e09db8349", "name": "NR-10 Complementar (SEP)", "metadata__unit_price":199}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T00:52:33.088916-03:00", "metadata__konviva_class_id":184, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "450a70ca-8ab5-4520-8a22-0e277359797d", "name": "NR-18 PEMT PTA", "metadata__unit_price":149}
{"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:09:33.088916-03:00", "metadata__konviva_class_id":259, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "863214e8-26e2-440b-854b-a0ced0164bbf", "name": "CIPA Grau de Risco 3 (teórico)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:30:33.088916-03:00", "metadata__konviva_class_id":270, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "96c2553a-d087-42ad-be5e-e960ea673c3d", "name": "NR-18 Sinaleiro e Amarrador de Cargas para Içamento", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:46:33.088916-03:00", "metadata__konviva_class_id":51, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "30bb357f-2f48-4764-93d1-ffe219cbc5d3", "name": "Reciclagem em NR-35 Trabalhos em Altura", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 720, "created_at": "2024-12-30T00:53:33.088916-03:00", "metadata__konviva_class_id":212, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "f7def039-bf27-496a-94ff-0667c9c0c0db", "name": "CIPA Grau de Risco 3", "metadata__unit_price":109}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:06:33.088916-03:00", "metadata__konviva_class_id":94, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "439e9a43-ab92-469a-a849-b6e824370f80", "name": "Noções em Primeiros Socorros", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:39:33.088916-03:00", "metadata__konviva_class_id":224, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "1c7b1cf0-6973-4271-9407-6e974f0094e9", "name": "PPR Programa de Proteção Respiratória", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:11:33.088916-03:00", "metadata__konviva_class_id":1, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "e1c44881-2fe3-484e-ada2-12b6bf5b9398", "name": "NR-35 Segurança nos Trabalhos em Altura (Teórico)", "metadata__unit_price":119}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:20:33.088916-03:00", "metadata__konviva_class_id":36, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "39f89a69-3d94-4dd6-9049-66e540fb2f32", "name": "Reciclagem em NR-10 Complementar (SEP)", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:32:33.088916-03:00", "metadata__konviva_class_id":34, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "5c119d4b-573c-4d8d-a99d-63756af2f4c5", "name": "NR-06 EPIs Equipamento de Proteção Individual", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:38:33.088916-03:00", "metadata__konviva_class_id":20, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "6d17d9cf-96be-42a4-bae5-75926e1e832a", "name": "NR-20 Exposição ao Benzeno ", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:28:33.088916-03:00", "metadata__konviva_class_id":200, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "3c27ea9c-9464-46a1-9717-8c1441793186", "name": "CIPA Grau de Risco 1", "metadata__unit_price":99}
{"updated_at": "2025-07-24T17:42:24.373883-03:00", "access_period": 360, "created_at": "2024-12-30T00:10:33.088916-03:00", "metadata__konviva_class_id":3, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "281198c2-f293-4acc-b96e-e4a2d5f6b73c", "name": "CIPA (descontinuado)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:29:33.088916-03:00", "metadata__konviva_class_id":28, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "d800d2a9-ae76-46de-be82-3e06ae6afcee", "name": "NR-20 Básico", "metadata__unit_price":149}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:13:33.088916-03:00", "metadata__konviva_class_id":23, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "f10c3283-7722-41c6-ba5d-222f9f4f48af", "name": "NR-11 Operador de Empilhadeira", "metadata__unit_price":149}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:17:33.088916-03:00", "metadata__konviva_class_id":18, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4866c068-577a-45b0-b41a-41a7dc6b9ab7", "name": "Combate a Incêndio", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:41:33.088916-03:00", "metadata__konviva_class_id":26, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "3b05b03c-8714-4f98-90e0-2a3ac4940035", "name": "NR-13 Vasos de Pressão e Unidades de Processo", "metadata__unit_price":199}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:21:33.088916-03:00", "metadata__konviva_class_id":218, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "38d8ba20-49a4-4c69-b674-b70a985eb76a", "name": "CIPA Grau de Risco 4", "metadata__unit_price":119}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:02:33.088916-03:00", "metadata__konviva_class_id":276, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade", "name": "Reciclagem em NR-10 SEP (08 horas)", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:07:33.088916-03:00", "metadata__konviva_class_id":21, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "4ea2498a-a6a9-4293-94d0-ceeb248e64b7", "name": "NR-10 Básico", "metadata__unit_price":199}
{"updated_at": "2025-07-24T17:42:14.453946-03:00", "access_period": 360, "created_at": "2024-12-30T00:57:33.088916-03:00", "metadata__konviva_class_id":274, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "b3d0e345-a8e5-4dc2-a3b0-218bc894ee55", "name": "Noções do Risco Elétrico"}
{"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2025-05-12T18:59:12.745375-03:00", "metadata__konviva_class_id":279, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "170884c0-f98e-4b58-8a32-7986f3b41076", "name": "NR-20 Básico - Instalação Classe 1"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:14:33.088916-03:00", "metadata__konviva_class_id":117, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "eb19c520-5546-4c57-898d-029c86e59fb6", "name": "Reciclagem em NR-33 Supervisores em Espaço Confinado", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:42:33.088916-03:00", "metadata__konviva_class_id":38, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "2b9a6e19-2e2d-4fc2-8924-b45d47057715", "name": "Reciclagem em NR-33 Trabalhos em Espaços Confinados", "metadata__unit_price":109}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:49:33.088916-03:00", "metadata__konviva_class_id":278, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "96c03c32-089c-4ccb-8aa1-73b0f49228b9", "name": "Lei Lucas: Primeiros Socorros nas Escolas", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:51:33.088916-03:00", "metadata__konviva_class_id":272, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "0e39c7af-a812-49aa-ac12-72f4e0ede8c9", "name": "Reciclagem em NR-18 PEMT Plataforma Elevatória Móvel de Trabalho", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:47:33.088916-03:00", "metadata__konviva_class_id":63, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "6a403773-aeac-4e6a-ac39-dc958e4be52a", "name": "Reciclagem em NR-11 Operador de Empilhadeira", "metadata__unit_price":99}
{"updated_at": "2025-07-04T18:09:49.470973+00:00", "access_period": 360, "created_at": "2024-12-30T00:04:33.088916-03:00", "metadata__konviva_class_id":166, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "4a0c4652-fcb3-4362-8fff-bc5ac3ba1cf3", "name": "Reciclagem em NR-11 Plataforma de Trabalho Elevatória (PTA)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 720, "created_at": "2024-12-30T00:55:33.088916-03:00", "metadata__konviva_class_id":206, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454", "name": "CIPA Grau de Risco 2", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:45:33.088916-03:00", "metadata__konviva_class_id":273, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "76a5ba94-e11c-48f5-88eb-9326df9be264", "name": "Reciclagem em NR-12 Máquinas e Equipamentos", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:15:33.088916-03:00", "metadata__konviva_class_id":19, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "a1a8727c-0519-4692-93e7-81dbe66e167f", "name": "Direção Defensiva (20 horas)", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:23:33.088916-03:00", "metadata__konviva_class_id":31, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "3f284753-85ce-4f53-8de7-cdfcdaf9515b", "name": "NR-33 Supervisor em Espaço Confinado", "metadata__unit_price":199}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:36:33.088916-03:00", "metadata__konviva_class_id":29, "tenant_id": "*", "cert": {"exp_interval": 700}, "sk": "0", "id": "95a1fcb9-ba16-4b3c-a59d-047ca32078ff", "name": "NR-20 Iniciação", "metadata__unit_price":99}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:33:33.088916-03:00", "metadata__konviva_class_id":194, "tenant_id": "*", "cert": {"exp_interval": 360}, "sk": "0", "id": "00ebdd8d-b4db-4437-8814-274811a4c469", "name": "Direção Defensiva (08 horas)", "metadata__unit_price":99}
{"updated_at": "2025-07-24T17:42:42.013895-03:00", "access_period": 360, "created_at": "2024-12-30T00:24:33.088916-03:00", "metadata__konviva_class_id":4, "tenant_id": "*", "cert":null, "sk": "0", "id": "386f7086-2871-436f-85f5-31d632fbf624", "name": "Manipulação de Alimentos (descontinuado)"}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 360, "created_at": "2024-12-30T00:08:33.088916-03:00", "metadata__konviva_class_id":35, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "c01ec8a2-0359-4351-befb-76c3577339e0", "name": "Reciclagem em NR-10 Básico (20 horas)", "metadata__unit_price":169}
{"updated_at": "2025-08-15T00:00:24.366947-03:00", "access_period": 365, "created_at": "2024-12-30T00:44:33.088916-03:00", "metadata__konviva_class_id":57, "tenant_id": "*", "cert": {"exp_interval": 720}, "sk": "0", "id": "0707270e-623f-486a-8dbd-d852377a208c", "name": "Reciclagem em NR-20 Intermediário", "metadata__unit_price":99}

View File

@@ -9,15 +9,15 @@ from aws_lambda_powertools.utilities.typing import LambdaContext
from routes.authorize import router as authorize
from routes.jwks import router as jwks
from routes.login import router as login
from routes.openid_configuration import router as openid_configuration
from routes.session import router as session
from routes.token import router as token
from routes.userinfo import router as userinfo
logger = Logger(__name__)
tracer = Tracer()
app = APIGatewayHttpResolver(enable_validation=True)
app.include_router(login)
app.include_router(session)
app.include_router(authorize)
app.include_router(jwks)
app.include_router(token)

View File

@@ -1,52 +0,0 @@
from datetime import timedelta
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError
from jose import jwt
from layercake.dateutils import now
from config import (
ISSUER,
JWT_ALGORITHM,
JWT_EXP_SECONDS,
JWT_SECRET,
OAUTH2_REFRESH_TOKEN_EXPIRES_IN,
)
def generate_jwt(user_id: str, email: str) -> str:
now_ = now()
payload = {
'sub': user_id,
'email': email,
'iat': int(now_.timestamp()),
'exp': int((now_ + timedelta(seconds=JWT_EXP_SECONDS)).timestamp()),
'iss': ISSUER,
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def generate_refresh_token(user_id: str) -> str:
now_ = now()
exp = now_ + timedelta(seconds=OAUTH2_REFRESH_TOKEN_EXPIRES_IN)
payload = {
'sub': user_id,
'iat': int(now_.timestamp()),
'exp': int(exp.timestamp()),
'iss': ISSUER,
'typ': 'refresh',
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def verify_jwt(token: str) -> dict:
payload = jwt.decode(
token,
JWT_SECRET,
algorithms=[JWT_ALGORITHM],
issuer=ISSUER,
options={
'require': ['exp', 'sub', 'iss'],
'leeway': 60,
},
)
return payload

View File

@@ -1,49 +1,40 @@
from http import HTTPStatus
from http.cookies import SimpleCookie
from urllib.parse import ParseResult, quote, urlencode, urlunparse
import jwt
from authlib.oauth2 import OAuth2Error
from authlib.oauth2.rfc6749 import errors
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import Response
from aws_lambda_powertools.event_handler.api_gateway import Router
from jose.exceptions import JWTError
from aws_lambda_powertools.event_handler.exceptions import BadRequestError
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from jose_ import verify_jwt
from boto3clients import dynamodb_client
from config import ISSUER, JWT_ALGORITHM, JWT_SECRET, OAUTH2_TABLE
from oauth2 import server
router = Router()
logger = Logger(__name__)
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
@router.get('/authorize')
def authorize():
current_event = router.current_event
cookies = _parse_cookies(current_event.get('cookies', []))
id_token = cookies.get('id_token')
continue_url = _build_continue_url(
current_event.path,
current_event.query_string_parameters,
)
session_id = cookies.get('session_id')
login_url = f'/login?continue={continue_url}'
try:
if not id_token:
raise ValueError('Missing id_token')
user = verify_jwt(id_token)
except (ValueError, JWTError):
return Response(
status_code=HTTPStatus.FOUND,
headers={'Location': login_url},
)
if not session_id:
raise BadRequestError('Missing session_id')
try:
user_id = verify_session(session_id)
grant = server.get_consent_grant(
request=router.current_event,
end_user={'id': user['sub']},
end_user={'id': user_id},
)
except jwt.exceptions.InvalidTokenError as err:
logger.exception(err)
raise BadRequestError(str(err))
except OAuth2Error as err:
logger.exception(err)
return dict(err.get_body())
@@ -51,7 +42,7 @@ def authorize():
try:
return server.create_authorization_response(
request=router.current_event,
grant_user={'id': user['sub']},
grant_user={'id': user_id},
grant=grant,
)
except errors.OAuth2Error as err:
@@ -59,6 +50,29 @@ def authorize():
return {}
def verify_session(session_id: str) -> str:
payload = jwt.decode(
session_id,
JWT_SECRET,
algorithms=[JWT_ALGORITHM],
issuer=ISSUER,
options={
'require': ['exp', 'sub', 'iss', 'sid'],
'leeway': 60,
},
)
oauth2_layer.collection.get_item(
KeyPair(
pk='SESSION',
sk=payload['sid'],
),
exc_cls=SessionRevokedError,
)
return payload['sub']
def _parse_cookies(cookies: list[str] | None) -> dict[str, str]:
parsed_cookies = {}
@@ -73,9 +87,6 @@ def _parse_cookies(cookies: list[str] | None) -> dict[str, str]:
return parsed_cookies
def _build_continue_url(
path: str,
query_string_parameters: dict,
) -> str:
query = urlencode(query_string_parameters)
return quote(urlunparse(ParseResult('', '', path, '', query, '')), safe='')
class SessionRevokedError(BadRequestError):
def __init__(self, *_):
super().__init__('Session revoked')

View File

@@ -1,91 +0,0 @@
from http import HTTPStatus
from typing import Annotated
from aws_lambda_powertools.event_handler import (
Response,
)
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError, NotFoundError
from aws_lambda_powertools.event_handler.openapi.params import Form, Param
from aws_lambda_powertools.shared.cookies import Cookie
from jinja2 import Environment, PackageLoader, select_autoescape
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from passlib.hash import pbkdf2_sha256
from boto3clients import dynamodb_client
from config import OAUTH2_TABLE
from jose_ import generate_jwt
router = Router()
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
templates = Environment(
loader=PackageLoader('app'),
autoescape=select_autoescape(['html']),
)
@router.get('/login', compress=True)
def login_form(continue_: Annotated[str, Param(alias='continue')]):
template = templates.get_template('login.html')
html = template.render(**{'continue': continue_})
return Response(
body=html,
status_code=HTTPStatus.OK,
content_type='text/html',
)
@router.post('/login')
def login(
username: Annotated[str, Form()],
password: Annotated[str, Form()],
continue_: Annotated[str, Form(alias='continue')],
):
user_id, password_hash = _get_user(username)
if not pbkdf2_sha256.verify(password, password_hash):
raise ForbiddenError('Invalid credentials')
jwt_token = generate_jwt(user_id, username)
return Response(
status_code=HTTPStatus.FOUND,
headers={
'Location': continue_,
},
cookies=[
Cookie(
name='id_token',
value=jwt_token,
http_only=True,
same_site=None,
),
],
)
def _get_user(username: str) -> tuple[str, str]:
r = oauth2_layer.collection.get_item(
# Post-migration: uncomment the following line
# KeyPair('EMAIL', username),
KeyPair('email', username),
exc_cls=EmailNotFoundError,
)
password = oauth2_layer.collection.get_item(
KeyPair(r['user_id'], 'PASSWORD'),
exc_cls=UserNotFoundError,
)
return r['user_id'], password['hash']
class EmailNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('Email not found')
class UserNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('User not found')

View File

@@ -0,0 +1,107 @@
from http import HTTPStatus
from typing import Annotated
from uuid import uuid4
import jwt
from aws_lambda_powertools.event_handler import (
Response,
)
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError, NotFoundError
from aws_lambda_powertools.event_handler.openapi.params import Body
from aws_lambda_powertools.shared.cookies import Cookie
from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from passlib.hash import pbkdf2_sha256
from boto3clients import dynamodb_client
from config import ISSUER, JWT_ALGORITHM, JWT_EXP_SECONDS, JWT_SECRET, OAUTH2_TABLE
router = Router()
oauth2_layer = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
@router.post('/session')
def session(
username: Annotated[str, Body()],
password: Annotated[str, Body()],
):
user_id, password_hash = _get_user(username)
if not pbkdf2_sha256.verify(password, password_hash):
raise ForbiddenError('Invalid credentials')
return Response(
status_code=HTTPStatus.FOUND,
cookies=[
Cookie(
name='session_id',
value=new_session(user_id),
http_only=True,
secure=True,
same_site=None,
)
],
)
def _get_user(username: str) -> tuple[str, str]:
sk = SortKey(username, path_spec='user_id')
user = oauth2_layer.collection.get_items(
KeyPair(pk='email', sk=sk, rename_key=sk.path_spec)
+ KeyPair(pk='cpf', sk=sk, rename_key=sk.path_spec),
flatten_top=False,
)
if not user:
raise UserNotFoundError()
password = oauth2_layer.collection.get_item(
KeyPair(user['user_id'], 'PASSWORD'),
exc_cls=UserNotFoundError,
)
return user['user_id'], password['hash']
def new_session(sub: str) -> str:
now_ = now()
sid = str(uuid4())
exp = ttl(start_dt=now_, seconds=JWT_EXP_SECONDS)
token = jwt.encode(
{
'sid': sid,
'sub': sub,
'iss': ISSUER,
'iat': int(now_.timestamp()),
'exp': exp,
},
JWT_SECRET,
algorithm=JWT_ALGORITHM,
)
with oauth2_layer.transact_writer() as transact:
transact.put(
item={
'id': 'SESSION',
'sk': sid,
'user_id': sub,
'ttl': exp,
'created_at': now_,
}
)
transact.put(
item={
'id': sub,
'sk': f'SESSION#{sid}',
'ttl': exp,
'created_at': now_,
}
)
return token
class UserNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('User not found')

View File

@@ -1,115 +0,0 @@
<!doctype html>
<html>
<head>
<title>EDUSEG®</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body
class="font-sans antialiased bg-black text-white flex items-center justify-center min-h-screen px-3"
>
<div class="w-full max-w-sm relative">
<div
aria-hidden="true"
class="absolute inset-0 grid grid-cols-2 opacity-20"
>
<div
class="blur-[106px] h-56 bg-gradient-to-br to-lime-400 from-lime-700"
></div>
<div
class="blur-[106px] h-42 bg-gradient-to-r from-lime-400 to-lime-600"
></div>
</div>
<div class="grid gap-5 relative z-1">
<div class="text-center space-y-1">
<span
class="border border-white/15 bg-white/5 px-2.5 py-3 rounded-xl inline-block"
><svg
width="18"
height="24"
viewBox="0 0 18 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="size-12"
>
<path
d="M16.2756 23.4353L8.93847 20.1298C8.7383 20.0015 8.48167 20.0015 8.27893 20.1298L0.941837 23.4353C0.533793 23.6945 0 23.4019 0 22.9194V1.12629C0.00256631 0.787535 0.277162 0.512939 0.615915 0.512939H16.6066C16.9454 0.512939 17.22 0.787535 17.22 1.12629V22.9194C17.22 23.4019 16.6862 23.6945 16.2781 23.4353H16.2756Z"
fill="#8CD366"
></path>
<path
d="M10.7274 3.71313H3.34668V6.41803H10.7274V3.71313Z"
fill="#2E3524"
></path>
<path
d="M9.42115 8.4939H3.34668V10.6496H9.42115V8.4939Z"
fill="#2E3524"
></path>
<path
d="M10.7274 12.7263H3.34668V15.4312H10.7274V12.7263Z"
fill="#2E3524"
></path>
<path
d="M12.9984 13.6731H12.9958C12.5111 13.6731 12.1182 14.066 12.1182 14.5508V14.5533C12.1182 15.0381 12.5111 15.431 12.9958 15.431H12.9984C13.4831 15.431 13.8761 15.0381 13.8761 14.5533V14.5508C13.8761 14.066 13.4831 13.6731 12.9984 13.6731Z"
fill="#2E3524"
></path></svg
></span>
<h1 class="text-3xl mt-6 font-semibold font-display text-balance">
Faça login
</h1>
<p class="text-white/50 text-sm">
Não tem uma conta?
<a href="" class="font-medium text-white">Cadastre-se</a>.
</p>
</div>
<form method="POST" action="/login" class="space-y-6">
<input name="continue" type="hidden" value="{{ continue }}" />
<div class="grid gap-2">
<label for="username" class="text-sm leading-none font-medium">
Email ou CPF
</label>
<input
type="text"
id="username"
name="username"
class="border border-white/15 bg-white/8 w-full rounded-lg px-3 py-2.5 shadow-sm outline-none focus-visible:border-white/30 focus-visible:ring-white/20 focus-visible:ring-3 transition"
required
/>
</div>
<div class="grid gap-2">
<div class="flex justify-between items-center">
<label for="password" class="text-sm leading-none font-medium">
Senha
</label>
<a href="#" class="text-sm" tabindex="-1">Esqueceu sua senha?</a>
</div>
<input
type="password"
id="password"
name="password"
class="border border-white/15 bg-white/8 w-full rounded-lg px-3 py-2.5 shadow-sm outline-none focus-visible:border-white/30 focus-visible:ring-white/20 focus-visible:ring-3 transition"
required
/>
</div>
<button
type="submit"
class="w-full text-sm font-medium text-black bg-lime-400 rounded-lg px-4 py-2 h-9"
>
Entrar
</button>
</form>
<p class="text-xs text-white/50 text-center">
Ao fazer login, você concorda com nossa
<a href="#" class="underline hover:no-underline" target="_blank">
política de privacidade </a
>.
</p>
</div>
</div>
</body>
</html>

View File

@@ -22,14 +22,11 @@ const schema = z.object({
type Schema = z.infer<typeof schema>
export function meta({}: Route.MetaArgs) {
return [
{ title: 'EDUSEG®' },
{ name: 'description', content: 'Welcome to React Router!' }
]
return [{ title: 'EDUSEG®' }]
}
export function loader({ context }: Route.LoaderArgs) {
return { message: context.cloudflare.env.VALUE_FROM_CLOUDFLARE }
return { message: context.cloudflare.env.ISSUER_URL }
}
export default function Home({ loaderData }: Route.ComponentProps) {
@@ -67,7 +64,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
<div className="grid gap-3">
<Label htmlFor="email">Email ou CPF</Label>
<Input id="email" type="text" required />
<Input id="email" {...register('username')} />
</div>
<div className="grid gap-3">
@@ -80,7 +77,7 @@ export default function Home({ loaderData }: Route.ComponentProps) {
Esqueceu sua senha?
</a>
</div>
<Input id="password" type="password" required />
<Input id="password" {...register('password')} />
</div>
<Button type="submit" className="w-full bg-lime-400 cursor-pointer">
Entrar

View File

@@ -8,7 +8,7 @@ export default function Layout() {
href="//eduseg.com.br"
className="flex items-center gap-0.5 absolute top-5 left-5 text-sm z-1"
>
<ChevronLeftIcon className="size-5" /> Página incial
<ChevronLeftIcon className="size-5" /> Página inicial
</a>
<div className="w-full max-w-sm relative z-1">
<Outlet />

View File

@@ -1,9 +1,14 @@
name = "id-saladeaula-digital"
compatibility_date = "2025-04-04"
main = "./workers/app.ts"
#routes = [
# { pattern = "id.saladeaula.digital", custom_domain = true }
#]
routes = [
{ pattern = "id.saladeaula.digital", custom_domain = true }
]
[vars]
ISSUER_URL = "https://58tkjsb308.execute-api.sa-east-1.amazonaws.com"
[observability.logs]
enabled = true

View File

@@ -14,7 +14,7 @@ Globals:
Architectures:
- x86_64
Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:91
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:92
Environment:
Variables:
TZ: America/Sao_Paulo
@@ -52,16 +52,10 @@ Resources:
- DynamoDBCrudPolicy:
TableName: !Ref OAuth2Table
Events:
Login:
Session:
Type: HttpApi
Properties:
Path: /login
Method: GET
ApiId: !Ref HttpApi
LoginPost:
Type: HttpApi
Properties:
Path: /login
Path: /session
Method: POST
ApiId: !Ref HttpApi
Authorize:
@@ -70,13 +64,13 @@ Resources:
Path: /authorize
Method: GET
ApiId: !Ref HttpApi
OpenidConfiguration:
OpenIDConfiguration:
Type: HttpApi
Properties:
Path: /.well-known/openid-configuration
Method: GET
ApiId: !Ref HttpApi
Jwks:
JWKS:
Type: HttpApi
Properties:
Path: /.well-known/jwks.json

View File

@@ -1,11 +1,14 @@
from http import HTTPMethod
from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer
from jose_ import generate_jwt
from routes.session import new_session
from ..conftest import HttpApiProxy, LambdaContext
CLIENT_ID = 'd72d4005-1fa7-4430-9754-80d5e2487bb6'
USER_ID = '357db1c5-7442-4075-98a3-fbe5c938a419'
def test_authorize(
app,
@@ -14,12 +17,7 @@ def test_authorize(
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
client_id = 'd72d4005-1fa7-4430-9754-80d5e2487bb6'
id_token = generate_jwt(
user_id='357db1c5-7442-4075-98a3-fbe5c938a419',
email='sergio@somosbeta.com.br',
)
session_id = new_session(USER_ID)
r = app.lambda_handler(
http_api_proxy(
@@ -27,21 +25,21 @@ def test_authorize(
method=HTTPMethod.GET,
query_string_parameters={
'response_type': 'code',
'client_id': client_id,
'client_id': CLIENT_ID,
'redirect_uri': 'https://localhost/callback',
'scope': 'openid offline_access',
'nonce': '123',
'state': '456',
},
cookies=[
f'id_token={id_token}; HttpOnly; Secure',
f'session_id={session_id}; HttpOnly; Secure',
],
),
lambda_context,
)
assert 'Location' in r['headers']
print(r)
# print(r)
r = dynamodb_persistence_layer.query(
key_cond_expr='#pk = :pk',
@@ -55,3 +53,34 @@ def test_authorize(
# One item was added from seeds
assert len(r['items']) == 3
def test_authorize_revoked(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
invalid_session_id = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIwNTgzNTBhYi02NGU1LTQ0MzEtYmQyNy01MGVhOWIxNmQxZGYiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiaWF0IjoxNzU1Mzk3Nzk5LCJleHAiOjE3NTUzOTg2OTl9.dDbiHYReVERbkNH2df4sXK2VIwT7G1KjNC5UrBuN6IQ'
r = app.lambda_handler(
http_api_proxy(
raw_path='/authorize',
method=HTTPMethod.GET,
query_string_parameters={
'response_type': 'code',
'client_id': CLIENT_ID,
'redirect_uri': 'https://localhost/callback',
'scope': 'openid offline_access',
'nonce': '123',
'state': '456',
},
cookies=[
f'session_id={invalid_session_id}; HttpOnly; Secure',
],
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.BAD_REQUEST

View File

@@ -1,49 +0,0 @@
from http import HTTPMethod
from urllib.parse import urlencode
from ..conftest import HttpApiProxy, LambdaContext
def test_html(
app,
seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.GET,
query_string_parameters={'continue': 'http://localhost'},
),
lambda_context,
)
# print(r)
def test_login(
app,
seeds,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/login',
method=HTTPMethod.POST,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
},
body=urlencode(
{
'username': 'sergio@somosbeta.com.br',
'password': 'pytest@123',
'continue': 'http://localhost',
}
),
),
lambda_context,
)
# print(r)

View File

@@ -0,0 +1,30 @@
from http import HTTPMethod
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
from ..conftest import HttpApiProxy, LambdaContext
def test_session(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/session',
method=HTTPMethod.POST,
body={
'username': '07879819908',
'password': 'pytest@123',
},
),
lambda_context,
)
assert len(r['cookies']) == 1
session = dynamodb_persistence_layer.collection.query(PartitionKey('SESSION'))
assert len(session['items']) == 1

View File

@@ -2,9 +2,8 @@
{"id": "OAUTH2", "sk": "CLIENT_ID#d72d4005-1fa7-4430-9754-80d5e2487bb6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"}
{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"}
// Post-migration: uncomment the following line
// {"id": "EMAIL", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
{"id": "cpf", "sk": "07879819908", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419"}
// User data
{"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "sk": "0", "name": "Sérgio R Siqueira", "email": "sergio@somosbeta.com.br"}

View File

@@ -344,18 +344,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
]
[[package]]
name = "ecdsa"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]]
name = "email-validator"
version = "2.2.0"
@@ -434,18 +422,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "jmespath"
version = "1.0.1"
@@ -481,7 +457,7 @@ wheels = [
[[package]]
name = "layercake"
version = "0.9.8"
version = "0.9.10"
source = { directory = "../layercake" }
dependencies = [
{ name = "arnparse" },
@@ -490,7 +466,6 @@ dependencies = [
{ name = "dictdiffer" },
{ name = "ftfy" },
{ name = "glom" },
{ name = "jinja2" },
{ name = "meilisearch" },
{ name = "orjson" },
{ name = "passlib" },
@@ -498,7 +473,6 @@ dependencies = [
{ name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" },
{ name = "pyjwt" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "pytz" },
{ name = "requests" },
{ name = "smart-open", extra = ["s3"] },
@@ -514,7 +488,6 @@ requires-dist = [
{ name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "ftfy", specifier = ">=6.3.1" },
{ name = "glom", specifier = ">=24.11.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "meilisearch", specifier = ">=0.34.0" },
{ name = "orjson", specifier = ">=3.10.15" },
{ name = "passlib", specifier = ">=1.7.4" },
@@ -522,7 +495,6 @@ requires-dist = [
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pyjwt", specifier = ">=2.10.1" },
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
{ name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -541,34 +513,6 @@ dev = [
{ name = "ruff", specifier = ">=0.11.1" },
]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]]
name = "meilisearch"
version = "0.36.0"
@@ -641,15 +585,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" },
]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]]
name = "pycparser"
version = "2.22"
@@ -812,25 +747,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
]
[[package]]
name = "python-jose"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ecdsa" },
{ name = "pyasn1" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
]
[package.optional-dependencies]
cryptography = [
{ name = "cryptography" },
]
[[package]]
name = "pytz"
version = "2025.2"
@@ -855,18 +771,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]]
name = "ruff"
version = "0.12.1"

View File

@@ -1,6 +1,6 @@
[project]
name = "layercake"
version = "0.9.9"
version = "0.9.10"
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
readme = "README.md"
authors = [
@@ -23,10 +23,8 @@ dependencies = [
"sqlite-utils>=3.38",
"dictdiffer>=0.9.0",
"unidecode>=1.4.0",
"python-jose[cryptography]>=3.5.0",
"authlib>=1.6.1",
"passlib>=1.7.4",
"jinja2>=3.1.6",
"pyjwt>=2.10.1",
]

58
layercake/uv.lock generated
View File

@@ -466,18 +466,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" },
]
[[package]]
name = "ecdsa"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]]
name = "email-validator"
version = "2.2.0"
@@ -687,7 +675,7 @@ wheels = [
[[package]]
name = "layercake"
version = "0.9.8"
version = "0.9.9"
source = { editable = "." }
dependencies = [
{ name = "arnparse" },
@@ -696,7 +684,6 @@ dependencies = [
{ name = "dictdiffer" },
{ name = "ftfy" },
{ name = "glom" },
{ name = "jinja2" },
{ name = "meilisearch" },
{ name = "orjson" },
{ name = "passlib" },
@@ -704,7 +691,6 @@ dependencies = [
{ name = "pydantic", extra = ["email"] },
{ name = "pydantic-extra-types" },
{ name = "pyjwt" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "pytz" },
{ name = "requests" },
{ name = "smart-open", extra = ["s3"] },
@@ -731,7 +717,6 @@ requires-dist = [
{ name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "ftfy", specifier = ">=6.3.1" },
{ name = "glom", specifier = ">=24.11.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "meilisearch", specifier = ">=0.34.0" },
{ name = "orjson", specifier = ">=3.10.15" },
{ name = "passlib", specifier = ">=1.7.4" },
@@ -739,7 +724,6 @@ requires-dist = [
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
{ name = "pyjwt", specifier = ">=2.10.1" },
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.5.0" },
{ name = "pytz", specifier = ">=2025.1" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
@@ -1084,15 +1068,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/97/84/0e410c20bbe9a504fc56e97908f13261c2b313d16cbb3b738556166f044a/py_partiql_parser-0.6.1-py2.py3-none-any.whl", hash = "sha256:ff6a48067bff23c37e9044021bf1d949c83e195490c17e020715e927fe5b2456", size = 23520, upload-time = "2024-12-25T22:06:39.106Z" },
]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]]
name = "pycparser"
version = "2.22"
@@ -1278,25 +1253,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
]
[[package]]
name = "python-jose"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ecdsa" },
{ name = "pyasn1" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
]
[package.optional-dependencies]
cryptography = [
{ name = "cryptography" },
]
[[package]]
name = "pytz"
version = "2025.2"
@@ -1529,18 +1485,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]]
name = "ruff"
version = "0.12.7"