diff --git a/apps/admin.saladeaula.digital/worker-configuration.d.ts b/apps/admin.saladeaula.digital/worker-configuration.d.ts index 1e63d02..bca9efc 100644 --- a/apps/admin.saladeaula.digital/worker-configuration.d.ts +++ b/apps/admin.saladeaula.digital/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 1aadef19c576560a2c23acd0e7ab9b2e) +// Generated by Wrangler by running `wrangler types` (hash: 58c6149f238624e8945df7f87e575516) // Runtime types generated with workerd@1.20251118.0 2025-04-04 declare namespace Cloudflare { interface GlobalProps { @@ -9,12 +9,12 @@ declare namespace Cloudflare { CLIENT_ID: "1db63660-063d-4280-b2ea-388aca4a9459"; SCOPE: "openid profile email offline_access apps:admin"; API_URL: "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"; - ISSUER_URL: "https://id.saladeaula.digital"; MEILI_HOST: "https://search.saladeaula.digital"; CLIENT_SECRET: string; - REDIRECT_URI: string; SESSION_SECRET: string; MEILI_API_KEY: string; + REDIRECT_URI: string; + ISSUER_URL: string; } } interface Env extends Cloudflare.Env {} diff --git a/apps/id.saladeaula.digital/app/routes/index.tsx b/apps/id.saladeaula.digital/app/routes/index.tsx index 607ab5e..60da65e 100644 --- a/apps/id.saladeaula.digital/app/routes/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/index.tsx @@ -109,7 +109,7 @@ export default function Index({}: Route.ComponentProps) { type: 'manual' }) } - }, [fetcher.data]) + }, [fetcher.data, setError]) return ( <> diff --git a/apps/id.saladeaula.digital/app/routes/register/index.tsx b/apps/id.saladeaula.digital/app/routes/register/index.tsx index d86e1c5..7cdbe11 100644 --- a/apps/id.saladeaula.digital/app/routes/register/index.tsx +++ b/apps/id.saladeaula.digital/app/routes/register/index.tsx @@ -2,7 +2,7 @@ import type { Route } from '../+types' import { PatternFormat } from 'react-number-format' import { zodResolver } from '@hookform/resolvers/zod' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { CheckCircle2Icon } from 'lucide-react' import { useForm } from 'react-hook-form' import { redirect, useFetcher } from 'react-router' @@ -44,7 +44,11 @@ export async function action({ request, context }: Route.ActionArgs) { signal: request.signal }) - throw redirect('/authorize', { headers: r.headers }) + if (r.ok) { + throw redirect('/authorize', { headers: r.headers }) + } + + return { ok: false, error: await r.json() } } export default function Signup({}: Route.ComponentProps) { @@ -63,6 +67,16 @@ export default function Signup({}: Route.ComponentProps) { }) } + useEffect(() => { + switch (fetcher.data?.error?.type) { + case 'EmailConflictError': + return setError('email', { + message: 'O endereço de email já está em uso', + type: 'manual' + }) + } + }, [fetcher.data, setError]) + return ( {user ? ( diff --git a/apps/id.saladeaula.digital/worker-configuration.d.ts b/apps/id.saladeaula.digital/worker-configuration.d.ts index 9b85f42..e477b2a 100644 --- a/apps/id.saladeaula.digital/worker-configuration.d.ts +++ b/apps/id.saladeaula.digital/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 20d12d2cb42a565d117b277533ca85a9) +// Generated by Wrangler by running `wrangler types` (hash: 7e2e7bff7e69c3350947dfc5bad66ee7) // Runtime types generated with workerd@1.20251118.0 2025-04-04 declare namespace Cloudflare { interface GlobalProps { @@ -7,6 +7,7 @@ declare namespace Cloudflare { } interface Env { ISSUER_URL: "https://58tkjsb308.execute-api.sa-east-1.amazonaws.com"; + APP_URL: string; } } interface Env extends Cloudflare.Env {} diff --git a/apps/saladeaula.digital/worker-configuration.d.ts b/apps/saladeaula.digital/worker-configuration.d.ts index 83ae441..fdb621f 100644 --- a/apps/saladeaula.digital/worker-configuration.d.ts +++ b/apps/saladeaula.digital/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 46c0a3878ceeb71c97bee6d456de6815) +// Generated by Wrangler by running `wrangler types` (hash: c05ef0b4d5ad34d62b7a9ac73fa403af) // Runtime types generated with workerd@1.20251118.0 2025-04-04 nodejs_compat declare namespace Cloudflare { interface GlobalProps { @@ -9,15 +9,15 @@ declare namespace Cloudflare { CLIENT_ID: "1a5483ab-4521-4702-9115-5857ac676851"; SCOPE: "openid profile email offline_access"; API_URL: "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"; - ISSUER_URL: "https://id.saladeaula.digital"; BUCKET_NAME: "saladeaula.digital"; BUCKET_ENDPOINT: "https://s3.sa-east-1.amazonaws.com"; MEILI_HOST: "https://search.saladeaula.digital"; CLIENT_SECRET: string; - REDIRECT_URI: string; SESSION_SECRET: string; MEILI_API_KEY: string; KONVIVA_SECRET_KEY: string; + REDIRECT_URI: string; + ISSUER_URL: string; } } interface Env extends Cloudflare.Env {} @@ -25,7 +25,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/enrollments-events/app/boto3clients.py b/enrollments-events/app/boto3clients.py index d40dff7..2130523 100644 --- a/enrollments-events/app/boto3clients.py +++ b/enrollments-events/app/boto3clients.py @@ -5,8 +5,8 @@ import boto3 if TYPE_CHECKING: from mypy_boto3_dynamodb.client import DynamoDBClient - from mypy_boto3_s3 import S3Client - from mypy_boto3_sesv2 import SESV2Client + from mypy_boto3_s3.client import S3Client + from mypy_boto3_sesv2.client import SESV2Client else: DynamoDBClient = object SESV2Client = object diff --git a/id.saladeaula.digital/app/boto3clients.py b/id.saladeaula.digital/app/boto3clients.py index 4d8e065..910b086 100644 --- a/id.saladeaula.digital/app/boto3clients.py +++ b/id.saladeaula.digital/app/boto3clients.py @@ -6,9 +6,11 @@ import boto3 if TYPE_CHECKING: from mypy_boto3_cognito_idp import CognitoIdentityProviderClient from mypy_boto3_dynamodb.client import DynamoDBClient + from mypy_boto3_sesv2.client import SESV2Client else: DynamoDBClient = object CognitoIdentityProviderClient = object + SESV2Client = object def get_dynamodb_client() -> DynamoDBClient: @@ -20,3 +22,4 @@ def get_dynamodb_client() -> DynamoDBClient: dynamodb_client: DynamoDBClient = get_dynamodb_client() idp_client: CognitoIdentityProviderClient = boto3.client('cognito-idp') +sesv2_client: SESV2Client = boto3.client('sesv2') diff --git a/id.saladeaula.digital/app/config.py b/id.saladeaula.digital/app/config.py index 13d7da2..1f71079 100644 --- a/id.saladeaula.digital/app/config.py +++ b/id.saladeaula.digital/app/config.py @@ -2,6 +2,8 @@ import os ISSUER: str = os.getenv('ISSUER') # type: ignore +EMAIL_SENDER = ('EDUSEG®', 'noreply@eduseg.com.br') + OAUTH2_TABLE: str = os.getenv('OAUTH2_TABLE') # type: ignore OAUTH2_REFRESH_TOKEN_EXPIRES_IN = 86_400 * 7 # 7 days OAUTH2_SCOPES_SUPPORTED: list[str] = [ diff --git a/id.saladeaula.digital/app/events/send_forgot_email.py b/id.saladeaula.digital/app/events/send_forgot_email.py new file mode 100644 index 0000000..d3932de --- /dev/null +++ b/id.saladeaula.digital/app/events/send_forgot_email.py @@ -0,0 +1,20 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.data_classes import ( + EventBridgeEvent, + event_source, +) +from aws_lambda_powertools.utilities.typing import LambdaContext + +from boto3clients import sesv2_client +from config import EMAIL_SENDER + +logger = Logger(__name__) + + +@event_source(data_class=EventBridgeEvent) +def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: + new_image = event.detail['new_image'] + # Key pattern `PASSWORD_RESET#{code}` + *_, code = new_image['sk'].split('#') + + return True diff --git a/id.saladeaula.digital/app/routes/forgot.py b/id.saladeaula.digital/app/routes/forgot.py index 3e80db2..3b37052 100644 --- a/id.saladeaula.digital/app/routes/forgot.py +++ b/id.saladeaula.digital/app/routes/forgot.py @@ -2,11 +2,12 @@ from typing import Annotated from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.openapi.params import Body +from layercake.extra_types import CpfStr from pydantic import EmailStr router = Router() @router.post('/forgot') -def forgot(email: Annotated[EmailStr, Body(embed=True)]): +def forgot(email: Annotated[EmailStr | CpfStr, Body(embed=True)]): return {} diff --git a/id.saladeaula.digital/pyproject.toml b/id.saladeaula.digital/pyproject.toml index 6901226..dd160c6 100644 --- a/id.saladeaula.digital/pyproject.toml +++ b/id.saladeaula.digital/pyproject.toml @@ -12,7 +12,7 @@ dev = [ "pytest>=8.4.1", "ruff>=0.12.1", "pytest-cov>=6.2.1", - "boto3-stubs[cognito-idp,essential]>=1.40.44", + "boto3-stubs[cognito-idp,essential,sesv2]>=1.40.44", ] diff --git a/id.saladeaula.digital/uv.lock b/id.saladeaula.digital/uv.lock index 47bf0df..0a3d19f 100644 --- a/id.saladeaula.digital/uv.lock +++ b/id.saladeaula.digital/uv.lock @@ -145,6 +145,9 @@ essential = [ { name = "mypy-boto3-s3" }, { name = "mypy-boto3-sqs" }, ] +sesv2 = [ + { name = "mypy-boto3-sesv2" }, +] [[package]] name = "botocore" @@ -426,7 +429,7 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "boto3-stubs", extra = ["cognito-idp", "essential"] }, + { name = "boto3-stubs", extra = ["cognito-idp", "essential", "sesv2"] }, { name = "jsonlines" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -438,7 +441,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ - { name = "boto3-stubs", extras = ["cognito-idp", "essential"], specifier = ">=1.40.44" }, + { name = "boto3-stubs", extras = ["cognito-idp", "essential", "sesv2"], specifier = ">=1.40.44" }, { name = "jsonlines", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-cov", specifier = ">=6.2.1" }, @@ -648,6 +651,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/a5/dba3384423834009bdd41c7021de5c663468a0e7bc4071cb301721e52a99/mypy_boto3_s3-1.40.26-py3-none-any.whl", hash = "sha256:6d055d16ef89a0133ade92f6b4f09603e4acc31a0f5e8f846edf4eb48f17b5a7", size = 82762, upload-time = "2025-09-08T20:12:19.338Z" }, ] +[[package]] +name = "mypy-boto3-sesv2" +version = "1.40.58" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/29/95a662122aa340cbf9a272639a20f3b77ef380e171c08d31da3cce0c46a3/mypy_boto3_sesv2-1.40.58.tar.gz", hash = "sha256:7b99f8df3821ec4a3408eea9de6de10bcea59397804fd8014bc6a5b8b1583913", size = 46559, upload-time = "2025-10-23T20:15:22.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/f2/79307d855d5c3f58806b27c6f41062f2e6985e31451d4ed690bbb357cec4/mypy_boto3_sesv2-1.40.58-py3-none-any.whl", hash = "sha256:c6cf7058e038553f3aa8f8111a6de067878462cbe22c9c8baf78cdd891a0e75f", size = 51779, upload-time = "2025-10-23T20:15:20.216Z" }, +] + [[package]] name = "mypy-boto3-sqs" version = "1.40.35" diff --git a/package-lock.json b/package-lock.json index 5f2ba6a..75e62ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ ], "devDependencies": { "prettier": "^3.6.2", - "turbo": "^2.6.0", + "turbo": "^2.6.2", "typescript": "5.9.2" }, "engines": { @@ -6404,27 +6404,27 @@ "license": "0BSD" }, "node_modules/turbo": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.6.1.tgz", - "integrity": "sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.6.2.tgz", + "integrity": "sha512-LiQAFS6iWvnY8ViGtoPgduWBeuGH9B32XR4p8H8jxU5PudwyHiiyf1jQW0fCC8gCCTz9itkIbqZLIyUu5AG33w==", "dev": true, "license": "MIT", "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "2.6.1", - "turbo-darwin-arm64": "2.6.1", - "turbo-linux-64": "2.6.1", - "turbo-linux-arm64": "2.6.1", - "turbo-windows-64": "2.6.1", - "turbo-windows-arm64": "2.6.1" + "turbo-darwin-64": "2.6.2", + "turbo-darwin-arm64": "2.6.2", + "turbo-linux-64": "2.6.2", + "turbo-linux-arm64": "2.6.2", + "turbo-windows-64": "2.6.2", + "turbo-windows-arm64": "2.6.2" } }, "node_modules/turbo-darwin-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.6.1.tgz", - "integrity": "sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.6.2.tgz", + "integrity": "sha512-nF9d/YAyrNkyXn9lp3ZtgXPb7fZsik3cUNe/sBvUO0G5YezUS/kDYYw77IdjizDzairz8pL2ITCTUreG2d5iZQ==", "cpu": [ "x64" ], @@ -6436,9 +6436,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.6.1.tgz", - "integrity": "sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-mmm0jFaVramST26XE1Lk2qjkjvLJHOe9f3TFjqY+aByjMK/ZmKE5WFPuCWo4L3xhwx+16T37rdPP//76loB3oA==", "cpu": [ "arm64" ], @@ -6450,9 +6450,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.6.1.tgz", - "integrity": "sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.6.2.tgz", + "integrity": "sha512-IUMHjkVRJDUABGpi+iS1Le59aOl5DX88U5UT/mKaE7nNEjG465+a8UtYno56cZnLP+C6BkX4I93LFgYf9syjGQ==", "cpu": [ "x64" ], @@ -6464,9 +6464,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.6.1.tgz", - "integrity": "sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.6.2.tgz", + "integrity": "sha512-0qQdZiimMUZj2Gfq87thYu0E02NaNcsB3lcEK/TD70Zzi7AxQoxye664Gis0Uao2j2L9/+05wC2btZ7SoFX3Gw==", "cpu": [ "arm64" ], @@ -6478,9 +6478,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.6.1.tgz", - "integrity": "sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.6.2.tgz", + "integrity": "sha512-BmMfFmt0VaoZL4NbtDq/dzGfjHsPoGU2+vFiZtkiYsttHY3fd/Dmgnu9PuRyJN1pv2M22q88rXO+dqYRHztLMw==", "cpu": [ "x64" ], @@ -6492,9 +6492,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.6.1.tgz", - "integrity": "sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.6.2.tgz", + "integrity": "sha512-0r4s4M/FgLxfjrdLPdqQUur8vZAtaWEi4jhkQ6wCIN2xzA9aee9IKwM53w7CQcjaLvWhT0AU7LTQHjFaHwxiKw==", "cpu": [ "arm64" ], diff --git a/package.json b/package.json index c8ee0f4..aa8765c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "prettier": "^3.6.2", - "turbo": "^2.6.0", + "turbo": "^2.6.2", "typescript": "5.9.2" }, "optionalDependencies": { diff --git a/users-events/app/boto3clients.py b/users-events/app/boto3clients.py index a702635..929fa25 100644 --- a/users-events/app/boto3clients.py +++ b/users-events/app/boto3clients.py @@ -1,7 +1,17 @@ import os +from typing import TYPE_CHECKING import boto3 +if TYPE_CHECKING: + from mypy_boto3_dynamodb.client import DynamoDBClient + from mypy_boto3_s3.client import S3Client + from mypy_boto3_sesv2.client import SESV2Client +else: + DynamoDBClient = object + S3Client = object + SESV2Client = object + def get_dynamodb_client(): if os.getenv('AWS_LAMBDA_FUNCTION_NAME'): @@ -10,6 +20,6 @@ def get_dynamodb_client(): return boto3.client('dynamodb', endpoint_url='http://127.0.0.1:8000') -dynamodb_client = get_dynamodb_client() -s3_client = boto3.client('s3') -sesv2_client = boto3.client('sesv2') +dynamodb_client: DynamoDBClient = get_dynamodb_client() +s3_client: S3Client = boto3.client('s3') +sesv2_client: SESV2Client = boto3.client('sesv2') diff --git a/users-events/pyproject.toml b/users-events/pyproject.toml index a1c976a..981ca3e 100644 --- a/users-events/pyproject.toml +++ b/users-events/pyproject.toml @@ -8,7 +8,7 @@ dependencies = ["layercake"] [dependency-groups] dev = [ - "boto3-stubs[essential]>=1.38.26", + "boto3-stubs[essential,sesv2]>=1.38.26", "jsonlines>=4.0.0", "pytest>=8.3.4", "pytest-cov>=6.0.0", diff --git a/users-events/uv.lock b/users-events/uv.lock index 5636132..80341eb 100644 --- a/users-events/uv.lock +++ b/users-events/uv.lock @@ -138,6 +138,9 @@ essential = [ { name = "mypy-boto3-s3" }, { name = "mypy-boto3-sqs" }, ] +sesv2 = [ + { name = "mypy-boto3-sesv2" }, +] [[package]] name = "botocore" @@ -601,6 +604,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/fa/251b651c18341c7491909994bd459b12ad05e13059d65bfa65d3afabdf8d/mypy_boto3_s3-1.38.26-py3-none-any.whl", hash = "sha256:1129d64be1aee863e04f0c92ac8d315578f13ccae64fa199b20ad0950d2b9616", size = 80321, upload-time = "2025-05-29T19:42:59.199Z" }, ] +[[package]] +name = "mypy-boto3-sesv2" +version = "1.38.46" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/02/7d6747098eebf2a8639f33a1cb963ddad4f320e823e6e626ec3e1aad4e5b/mypy_boto3_sesv2-1.38.46.tar.gz", hash = "sha256:efebb7e6495b6c38cf1549833b4f8f889b12b831206013d9d11f41aa1d495a45", size = 42715, upload-time = "2025-06-27T20:34:02.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/83/00e89c38eade7dd9915599eefea30bcbe259c2bbb4bb8c08a02e4af6486c/mypy_boto3_sesv2-1.38.46-py3-none-any.whl", hash = "sha256:a03e979b0a7489d877f9c7912ea82de863f59a76014715edbcdbc1ab69ce00d8", size = 47335, upload-time = "2025-06-27T20:33:57.211Z" }, +] + [[package]] name = "mypy-boto3-sqs" version = "1.38.0" @@ -1121,7 +1133,7 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "boto3-stubs", extra = ["essential"] }, + { name = "boto3-stubs", extra = ["essential", "sesv2"] }, { name = "jsonlines" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1133,7 +1145,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }] [package.metadata.requires-dev] dev = [ - { name = "boto3-stubs", extras = ["essential"], specifier = ">=1.38.26" }, + { name = "boto3-stubs", extras = ["essential", "sesv2"], specifier = ">=1.38.26" }, { name = "jsonlines", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-cov", specifier = ">=6.0.0" },