diff --git a/api.saladeaula.digital/app/app.py b/api.saladeaula.digital/app/app.py index 0a0c6d0..2e2ce59 100644 --- a/api.saladeaula.digital/app/app.py +++ b/api.saladeaula.digital/app/app.py @@ -43,7 +43,9 @@ app.include_router(users.emails, prefix='/users') app.include_router(users.add, prefix='/users') app.include_router(users.orgs, prefix='/users') app.include_router(orders.router, prefix='/orders') +app.include_router(orgs.admins, prefix='/orgs') app.include_router(orgs.custom_pricing, prefix='/orgs') +app.include_router(orgs.scheduled, prefix='/orgs') @app.get('/health') diff --git a/api.saladeaula.digital/app/routes/orgs/__init__.py b/api.saladeaula.digital/app/routes/orgs/__init__.py index e751bfe..eb8c307 100644 --- a/api.saladeaula.digital/app/routes/orgs/__init__.py +++ b/api.saladeaula.digital/app/routes/orgs/__init__.py @@ -1,4 +1,5 @@ from .admins import router as admins from .custom_pricing import router as custom_pricing +from .enrollments.scheduled import router as scheduled -__all__ = ['admins', 'custom_pricing'] +__all__ = ['admins', 'custom_pricing', 'scheduled'] diff --git a/api.saladeaula.digital/app/routes/orgs/admins.py b/api.saladeaula.digital/app/routes/orgs/admins.py index 2637650..f7ec362 100644 --- a/api.saladeaula.digital/app/routes/orgs/admins.py +++ b/api.saladeaula.digital/app/routes/orgs/admins.py @@ -2,7 +2,7 @@ from aws_lambda_powertools.event_handler.api_gateway import Router from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from boto3clients import dynamodb_client -from config import COURSE_TABLE, USER_TABLE +from config import USER_TABLE router = Router() dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @@ -11,7 +11,7 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) @router.get('//admins') def get_admins(org_id: str): return dyn.collection.query( + # Post-migration: rename `admins` to `ADMIN` KeyPair(org_id, 'admins#'), - table_name=COURSE_TABLE, limit=100, ) diff --git a/api.saladeaula.digital/app/routes/orgs/custom_pricing.py b/api.saladeaula.digital/app/routes/orgs/custom_pricing.py index 4f6eb85..21b7115 100644 --- a/api.saladeaula.digital/app/routes/orgs/custom_pricing.py +++ b/api.saladeaula.digital/app/routes/orgs/custom_pricing.py @@ -2,16 +2,15 @@ from aws_lambda_powertools.event_handler.api_gateway import Router from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey from boto3clients import dynamodb_client -from config import COURSE_TABLE, USER_TABLE +from config import COURSE_TABLE router = Router() -dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) +dyn = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) -@router.get('//custompricing') +@router.get('//custom-pricing') def get_custom_pricing(org_id: str): return dyn.collection.query( PartitionKey(f'CUSTOM_PRICING#ORG#{org_id}'), - table_name=COURSE_TABLE, limit=100, ) diff --git a/api.saladeaula.digital/app/routes/orgs/enrollments/__init__.py b/api.saladeaula.digital/app/routes/orgs/enrollments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api.saladeaula.digital/app/routes/orgs/enrollments/scheduled.py b/api.saladeaula.digital/app/routes/orgs/enrollments/scheduled.py new file mode 100644 index 0000000..43d8828 --- /dev/null +++ b/api.saladeaula.digital/app/routes/orgs/enrollments/scheduled.py @@ -0,0 +1,22 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler.api_gateway import Router +from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey + +from boto3clients import dynamodb_client +from config import ENROLLMENT_TABLE + +logger = Logger(__name__) +router = Router() +dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) + + +@router.get('//enrollments/scheduled') +def scheduled(org_id: str): + start_key = router.current_event.get_query_string_value('start_key', None) + + return dyn.collection.query( + # Post-migration: rename `scheduled_items` to `SCHEDULED#ORG#{org_id}` + key=PartitionKey(f'scheduled_items#{org_id}'), + start_key=start_key, + limit=150, + ) diff --git a/api.saladeaula.digital/tests/routes/test_orgs.py b/api.saladeaula.digital/tests/routes/test_orgs.py new file mode 100644 index 0000000..41b8c1b --- /dev/null +++ b/api.saladeaula.digital/tests/routes/test_orgs.py @@ -0,0 +1,39 @@ +import json +from http import HTTPMethod, HTTPStatus + +from ..conftest import HttpApiProxy, LambdaContext + + +def test_get_admins( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/orgs/f6000f79-6e5c-49a0-952f-3bda330ef278/admins', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK + + r = json.loads(r['body']) + assert len(r['items']) == 1 + + +def test_get_scheduled( + app, + seeds, + http_api_proxy: HttpApiProxy, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/orgs/1234/enrollments/scheduled', + method=HTTPMethod.GET, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.OK diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl index f1d8910..990448b 100644 --- a/api.saladeaula.digital/tests/seeds.jsonl +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -13,6 +13,9 @@ {"id": "2a8963fc-4694-4fe2-953a-316d1b10f1f5", "sk": "0", "name": "pytest", "cnpj": "04978826000180"} {"id": "f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "0", "name": "Banco do Brasil", "cnpj": "00000000000191"} +// Org admins +{"id": "f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "admins#15bacf02-1535-4bee-9022-19d106fd7518", "name": "Chester Bennington", "email": "chester@linkinpark.com"} + {"id": "orgmembers#f6000f79-6e5c-49a0-952f-3bda330ef278", "sk": "15bacf02-1535-4bee-9022-19d106fd7518"} // Indicies diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.admins._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.admins._index/route.tsx index 6535ed5..0e59c7b 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.admins._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.admins._index/route.tsx @@ -1,10 +1,44 @@ import type { Route } from './+types' +import { + EllipsisVerticalIcon, + PencilIcon, + UserRoundMinusIcon +} from 'lucide-react' +import { Suspense } from 'react' +import { Await, NavLink } from 'react-router' + +import { Abbr } from '@/components/abbr' +import { request as req } from '@/lib/request' +import { Skeleton } from '@repo/ui/components/skeleton' +import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar' +import { Button } from '@repo/ui/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '@repo/ui/components/ui/dropdown-menu' +import { Spinner } from '@repo/ui/components/ui/spinner' +import { initials } from '@repo/ui/lib/utils' + export function meta({}: Route.MetaArgs) { return [{ title: 'Gestores' }] } -export default function Route() { +export async function loader({ context, request, params }: Route.LoaderArgs) { + const data = req({ + url: `/orgs/${params.orgid}/admins`, + context, + request + }).then((r) => r.json()) + + return { + data + } +} + +export default function Route({ loaderData: { data } }) { return ( <>
@@ -13,6 +47,80 @@ export default function Route() { Adicione gestores e organize sua equipe de forma prática.

+ + }> + + {({ items }) => { + return ( +
+ {items.map(({ sk, name, email }, index) => { + const [_, id] = sk.split('#') + return ( +
+ + + + + + e.preventDefault()} + > + + {({ isPending }) => ( + <> + {isPending ? : } + Editar + + )} + + + + Revogar privilégios + + + + +
+
+
+ + + {initials(name)} + + +
+ +
+

+ {name} +

+

+ {email} +

+
+
+
+ ) + })} +
+ ) + }} +
+
) } diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx index d1033ed..23f0f6c 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx @@ -58,7 +58,7 @@ export async function loader({ context, request, params }: Route.LoaderArgs) { }) const customPricing = req({ - url: `/orgs/${params.orgid}/custompricing`, + url: `/orgs/${params.orgid}/custom-pricing`, context, request }).then((r) => r.json()) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.scheduled/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.scheduled/route.tsx index 1bd9c59..197a7ed 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.scheduled/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.scheduled/route.tsx @@ -1,10 +1,28 @@ import type { Route } from './+types' +import { Suspense } from 'react' + +import { request as req } from '@/lib/request' +import { Skeleton } from '@repo/ui/components/skeleton' +import { Await } from 'react-router' + export function meta({}: Route.MetaArgs) { return [{ title: 'Matrículas agendadas' }] } -export default function Route() { +export async function loader({ context, request, params }: Route.LoaderArgs) { + const data = req({ + url: `/orgs/${params.orgid}/enrollments/scheduled`, + context, + request + }).then((r) => r.json()) + + return { + data + } +} + +export default function Route({ loaderData: { data } }) { return ( <>
@@ -16,6 +34,12 @@ export default function Route() { matricule imediatamente.

+ + }> + + {(resolved) => <>...{console.log(resolved)}} + + ) } diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.$id.emails/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.$id.emails/route.tsx index 021f16c..18ab5e0 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.$id.emails/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users.$id.emails/route.tsx @@ -13,7 +13,6 @@ import { CardHeader, CardTitle } from '@repo/ui/components/ui/card' -import { Item, ItemContent, ItemGroup } from '@repo/ui/components/ui/item' import { Kbd } from '@repo/ui/components/ui/kbd' import { NativeSelect, diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/columns.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/columns.tsx index 5e536f1..e745edc 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/columns.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/columns.tsx @@ -3,7 +3,6 @@ import { formatCPF } from '@brazilian-utils/brazilian-utils' import { type ColumnDef } from '@tanstack/react-table' import { - ArrowRight, EllipsisVerticalIcon, PencilIcon, UserRoundMinusIcon diff --git a/http-api/app/routes/users/add.py b/http-api/app/routes/users/add.py index 2208f6d..e5f4ac7 100644 --- a/http-api/app/routes/users/add.py +++ b/http-api/app/routes/users/add.py @@ -77,6 +77,7 @@ def add(user: User): cond_expr='attribute_not_exists(sk)', exc_cls=CPFConflictError, ) + transact.put( item={ 'id': 'email', @@ -91,12 +92,22 @@ def add(user: User): item={ 'sk': '0', 'tenant_id': {org.id}, + 'email_verified': False, # Post-migration: uncomment the following line # 'createDate': now_, 'createDate': now_, } | user.model_dump() ) + transact.put( + item={ + 'id': user_id, + 'sk': f'emails#{user.email}', + 'email_verified': False, + 'email_primary': True, + 'created_at': now_, + } + ) transact.put( item={ 'id': user.id,