add test mode to enrollments
This commit is contained in:
@@ -28,6 +28,7 @@ from config import (
|
|||||||
)
|
)
|
||||||
from exceptions import (
|
from exceptions import (
|
||||||
ConflictError,
|
ConflictError,
|
||||||
|
NotAcceptableError,
|
||||||
OrderNotFoundError,
|
OrderNotFoundError,
|
||||||
SubscriptionConflictError,
|
SubscriptionConflictError,
|
||||||
SubscriptionFrozenError,
|
SubscriptionFrozenError,
|
||||||
@@ -47,6 +48,9 @@ class DeduplicationConflictError(ConflictError): ...
|
|||||||
class SeatNotFoundError(NotFoundError): ...
|
class SeatNotFoundError(NotFoundError): ...
|
||||||
|
|
||||||
|
|
||||||
|
class TestModeRequiredError(NotAcceptableError): ...
|
||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
id: str | UUID4
|
id: str | UUID4
|
||||||
name: NameStr
|
name: NameStr
|
||||||
@@ -91,6 +95,7 @@ def enroll(
|
|||||||
org_id: Annotated[str | UUID4, Body(embed=True)],
|
org_id: Annotated[str | UUID4, Body(embed=True)],
|
||||||
enrollments: Annotated[tuple[Enrollment, ...], Body(embed=True)],
|
enrollments: Annotated[tuple[Enrollment, ...], Body(embed=True)],
|
||||||
subscription: Annotated[Subscription | None, Body(embed=True)] = None,
|
subscription: Annotated[Subscription | None, Body(embed=True)] = None,
|
||||||
|
test_mode: Annotated[bool, Body(embed=True)] = False,
|
||||||
):
|
):
|
||||||
now_ = now()
|
now_ = now()
|
||||||
created_by: Authenticated = router.context['user']
|
created_by: Authenticated = router.context['user']
|
||||||
@@ -106,6 +111,7 @@ def enroll(
|
|||||||
'org': Org.model_validate(org),
|
'org': Org.model_validate(org),
|
||||||
'created_by': created_by,
|
'created_by': created_by,
|
||||||
'subscription': subscription,
|
'subscription': subscription,
|
||||||
|
'test_mode': test_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
immediate = [e for e in enrollments if not e.scheduled_for]
|
immediate = [e for e in enrollments if not e.scheduled_for]
|
||||||
@@ -125,12 +131,13 @@ def enroll(
|
|||||||
'cause': r.cause,
|
'cause': r.cause,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expires_after_days = 7 if test_mode else 30 * 3
|
||||||
item = {
|
item = {
|
||||||
'id': f'SUBMISSION#ORG#{org_id}',
|
'id': f'SUBMISSION#ORG#{org_id}',
|
||||||
'sk': now_,
|
'sk': now_,
|
||||||
'enrolled': list(map(fmt, now_out)) if now_out else None,
|
'enrolled': list(map(fmt, now_out)) if now_out else None,
|
||||||
'scheduled': list(map(fmt, later_out)) if later_out else None,
|
'scheduled': list(map(fmt, later_out)) if later_out else None,
|
||||||
'ttl': ttl(start_dt=now_, days=30 * 3),
|
'ttl': ttl(start_dt=now_, days=expires_after_days),
|
||||||
'created_by': {
|
'created_by': {
|
||||||
'id': created_by.id,
|
'id': created_by.id,
|
||||||
'name': created_by.name,
|
'name': created_by.name,
|
||||||
@@ -151,6 +158,7 @@ Context = TypedDict(
|
|||||||
'org': Org,
|
'org': Org,
|
||||||
'created_by': Authenticated,
|
'created_by': Authenticated,
|
||||||
'subscription': NotRequired[Subscription],
|
'subscription': NotRequired[Subscription],
|
||||||
|
'test_mode': NotRequired[bool],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -161,6 +169,7 @@ def enroll_now(enrollment: Enrollment, context: Context):
|
|||||||
course = enrollment.course
|
course = enrollment.course
|
||||||
seat = enrollment.seat
|
seat = enrollment.seat
|
||||||
org = context['org']
|
org = context['org']
|
||||||
|
test_mode = context.get('test_mode')
|
||||||
subscription = context.get('subscription')
|
subscription = context.get('subscription')
|
||||||
created_by = context['created_by']
|
created_by = context['created_by']
|
||||||
lock_hash = md5_hash(f'{user.id}{course.id}')
|
lock_hash = md5_hash(f'{user.id}{course.id}')
|
||||||
@@ -194,7 +203,16 @@ def enroll_now(enrollment: Enrollment, context: Context):
|
|||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
| ({'subscription_covered': True} if subscription else {})
|
| ({'subscription_covered': True} if subscription else {})
|
||||||
|
| ({'is_test': True} if test_mode else {})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if test_mode:
|
||||||
|
transact.condition(
|
||||||
|
key=KeyPair(str(org.id), 'METADATA#TEST_MODE'),
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
|
exc_cls=TestModeRequiredError,
|
||||||
|
)
|
||||||
|
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': enrollment.id,
|
'id': enrollment.id,
|
||||||
@@ -344,6 +362,7 @@ def _enroll_later(enrollment: Enrollment, context: Context):
|
|||||||
scheduled_for = _date_to_midnight(enrollment.scheduled_for) # type: ignore
|
scheduled_for = _date_to_midnight(enrollment.scheduled_for) # type: ignore
|
||||||
dedup_window = enrollment.deduplication_window
|
dedup_window = enrollment.deduplication_window
|
||||||
org = context['org']
|
org = context['org']
|
||||||
|
test_mode = context.get('test_mode')
|
||||||
subscription = context.get('subscription')
|
subscription = context.get('subscription')
|
||||||
created_by = context['created_by']
|
created_by = context['created_by']
|
||||||
lock_hash = md5_hash(f'{user.id}{course.id}')
|
lock_hash = md5_hash(f'{user.id}{course.id}')
|
||||||
@@ -371,6 +390,7 @@ def _enroll_later(enrollment: Enrollment, context: Context):
|
|||||||
'scheduled_at': now_,
|
'scheduled_at': now_,
|
||||||
}
|
}
|
||||||
| ({'seat': seat.model_dump()} if seat else {})
|
| ({'seat': seat.model_dump()} if seat else {})
|
||||||
|
| ({'is_test': True} if test_mode else {})
|
||||||
| (
|
| (
|
||||||
{'dedup_window_offset_days': dedup_window.offset_days}
|
{'dedup_window_offset_days': dedup_window.offset_days}
|
||||||
if dedup_window
|
if dedup_window
|
||||||
@@ -385,6 +405,13 @@ def _enroll_later(enrollment: Enrollment, context: Context):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if test_mode:
|
||||||
|
transact.condition(
|
||||||
|
key=KeyPair(str(org.id), 'METADATA#TEST_MODE'),
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
|
exc_cls=TestModeRequiredError,
|
||||||
|
)
|
||||||
|
|
||||||
if seat:
|
if seat:
|
||||||
transact.condition(
|
transact.condition(
|
||||||
key=KeyPair(str(seat.order_id), '0'),
|
key=KeyPair(str(seat.order_id), '0'),
|
||||||
|
|||||||
@@ -277,6 +277,14 @@ def checkout(payload: Checkout):
|
|||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
transact.put(
|
||||||
|
item={
|
||||||
|
'id': order_id,
|
||||||
|
'sk': 'SCHEDULED#AUTO_CLEANUP',
|
||||||
|
'ttl': ttl(start_dt=now_, days=7),
|
||||||
|
'created_at': now_,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
body={'id': order_id},
|
body={'id': order_id},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Kbd } from '@repo/ui/components/ui/kbd'
|
|||||||
import { headers, sortings, statuses } from '@repo/ui/routes/enrollments/data'
|
import { headers, sortings, statuses } from '@repo/ui/routes/enrollments/data'
|
||||||
import { createSearch } from '@repo/util/meili'
|
import { createSearch } from '@repo/util/meili'
|
||||||
|
|
||||||
|
import { workspaceContext } from '@/middleware/workspace'
|
||||||
import { columns, type Enrollment } from './columns'
|
import { columns, type Enrollment } from './columns'
|
||||||
|
|
||||||
export function meta({}: Route.MetaArgs) {
|
export function meta({}: Route.MetaArgs) {
|
||||||
@@ -26,6 +27,7 @@ export function meta({}: Route.MetaArgs) {
|
|||||||
|
|
||||||
export async function loader({ params, context, request }: Route.LoaderArgs) {
|
export async function loader({ params, context, request }: Route.LoaderArgs) {
|
||||||
const cloudflare = context.get(cloudflareContext)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
|
const { test_mode } = context.get(workspaceContext)
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const { orgid } = params
|
const { orgid } = params
|
||||||
const query = searchParams.get('q') || ''
|
const query = searchParams.get('q') || ''
|
||||||
@@ -36,7 +38,9 @@ export async function loader({ params, context, request }: Route.LoaderArgs) {
|
|||||||
const page = Number(searchParams.get('p')) + 1
|
const page = Number(searchParams.get('p')) + 1
|
||||||
const hitsPerPage = Number(searchParams.get('perPage')) || 25
|
const hitsPerPage = Number(searchParams.get('perPage')) || 25
|
||||||
|
|
||||||
let builder = new MeiliSearchFilterBuilder().where('org_id', '=', orgid)
|
let builder = new MeiliSearchFilterBuilder()
|
||||||
|
.where('org_id', '=', orgid)
|
||||||
|
.where('is_test', 'exists', test_mode)
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
builder = builder.where('status', 'in', status.split(','))
|
builder = builder.where('status', 'in', status.split(','))
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
import type { Route } from './+types/route'
|
import type { Route } from './+types/route'
|
||||||
|
|
||||||
import { pick } from 'ramda'
|
import { formatCNPJ } from '@brazilian-utils/brazilian-utils'
|
||||||
import {
|
import {
|
||||||
CalendarIcon,
|
|
||||||
PlusCircleIcon,
|
|
||||||
BuildingIcon,
|
BuildingIcon,
|
||||||
CheckIcon
|
CalendarIcon,
|
||||||
|
CheckIcon,
|
||||||
|
PlusCircleIcon
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { MeiliSearchFilterBuilder } from 'meilisearch-helper'
|
import { MeiliSearchFilterBuilder } from 'meilisearch-helper'
|
||||||
|
import { pick } from 'ramda'
|
||||||
import { Suspense, useState } from 'react'
|
import { Suspense, useState } from 'react'
|
||||||
import { Await, Outlet, useSearchParams } from 'react-router'
|
import { Await, Outlet, useSearchParams } from 'react-router'
|
||||||
import { formatCNPJ } from '@brazilian-utils/brazilian-utils'
|
|
||||||
|
|
||||||
import { cloudflareContext } from '@repo/auth/context'
|
import { cloudflareContext } from '@repo/auth/context'
|
||||||
|
import { Abbr } from '@repo/ui/components/abbr'
|
||||||
import { DataTable, DataTableViewOptions } from '@repo/ui/components/data-table'
|
import { DataTable, DataTableViewOptions } from '@repo/ui/components/data-table'
|
||||||
|
import { ExportMenu } from '@repo/ui/components/export-menu'
|
||||||
import { FacetedFilter } from '@repo/ui/components/faceted-filter'
|
import { FacetedFilter } from '@repo/ui/components/faceted-filter'
|
||||||
import { RangeCalendarFilter } from '@repo/ui/components/range-calendar-filter'
|
import { RangeCalendarFilter } from '@repo/ui/components/range-calendar-filter'
|
||||||
import { SearchForm } from '@repo/ui/components/search-form'
|
|
||||||
import { SearchFilter } from '@repo/ui/components/search-filter'
|
import { SearchFilter } from '@repo/ui/components/search-filter'
|
||||||
|
import { SearchForm } from '@repo/ui/components/search-form'
|
||||||
import { Skeleton } from '@repo/ui/components/skeleton'
|
import { Skeleton } from '@repo/ui/components/skeleton'
|
||||||
import { Kbd } from '@repo/ui/components/ui/kbd'
|
|
||||||
import { ExportMenu } from '@repo/ui/components/export-menu'
|
|
||||||
import { createSearch } from '@repo/util/meili'
|
|
||||||
import { headers, sortings, statuses } from '@repo/ui/routes/enrollments/data'
|
|
||||||
import { cn, initials } from '@repo/ui/lib/utils'
|
|
||||||
import { CommandItem } from '@repo/ui/components/ui/command'
|
|
||||||
import { Button } from '@repo/ui/components/ui/button'
|
|
||||||
import { Separator } from '@repo/ui/components/ui/separator'
|
|
||||||
import { Badge } from '@repo/ui/components/ui/badge'
|
|
||||||
import { Abbr } from '@repo/ui/components/abbr'
|
|
||||||
import { Spinner } from '@repo/ui/components/ui/spinner'
|
|
||||||
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
|
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
|
||||||
|
import { Badge } from '@repo/ui/components/ui/badge'
|
||||||
|
import { Button } from '@repo/ui/components/ui/button'
|
||||||
|
import { CommandItem } from '@repo/ui/components/ui/command'
|
||||||
|
import { Kbd } from '@repo/ui/components/ui/kbd'
|
||||||
|
import { Separator } from '@repo/ui/components/ui/separator'
|
||||||
|
import { Spinner } from '@repo/ui/components/ui/spinner'
|
||||||
|
import { cn, initials } from '@repo/ui/lib/utils'
|
||||||
|
import { headers, sortings, statuses } from '@repo/ui/routes/enrollments/data'
|
||||||
|
import { createSearch } from '@repo/util/meili'
|
||||||
|
|
||||||
import { columns, type Enrollment } from './columns'
|
import { columns, type Enrollment } from './columns'
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export async function loader({ context, request }: Route.LoaderArgs) {
|
|||||||
const page = Number(searchParams.get('p')) + 1
|
const page = Number(searchParams.get('p')) + 1
|
||||||
const hitsPerPage = Number(searchParams.get('perPage')) || 25
|
const hitsPerPage = Number(searchParams.get('perPage')) || 25
|
||||||
|
|
||||||
let builder = new MeiliSearchFilterBuilder()
|
let builder = new MeiliSearchFilterBuilder().where('is_test', 'exists', false)
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
builder = builder.where('status', 'in', status.split(','))
|
builder = builder.where('status', 'in', status.split(','))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
new_image = event.detail['new_image']
|
new_image = event.detail['new_image']
|
||||||
# Key pattern `BILLING#ORG#{org_id}`
|
# Key pattern `BILLING#ORG#{org_id}`
|
||||||
*_, org_id = new_image['id'].split('#')
|
*_, org_id = new_image['id'].split('#')
|
||||||
# Key pattern `START#{start_period}#END#{v}
|
# Key pattern `START#{start_period}#END#{end_period}
|
||||||
_, start_period, _, end_period, *_ = new_image['sk'].split('#')
|
_, start_period, _, end_period, *_ = new_image['sk'].split('#')
|
||||||
|
|
||||||
emailmsg = Message(
|
emailmsg = Message(
|
||||||
|
|||||||
@@ -102,16 +102,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
transact.delete(
|
transact.delete(
|
||||||
key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'),
|
key=KeyPair(order_id, 'CREDIT_CARD#PAYMENT_INTENT'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if test_mode:
|
|
||||||
transact.put(
|
|
||||||
item={
|
|
||||||
'id': order_id,
|
|
||||||
'sk': 'SCHEDULED#SELF_DESTRUCTION',
|
|
||||||
'ttl': ttl(start_dt=now_, days=7),
|
|
||||||
'created_at': now_,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
"""
|
|
||||||
Stopgap events. Everything here is a quick fix and should be replaced with
|
|
||||||
proper solutions.
|
|
||||||
"""
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
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 layercake.dynamodb import (
|
|
||||||
DynamoDBPersistenceLayer,
|
|
||||||
KeyPair,
|
|
||||||
)
|
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
|
||||||
from config import ENROLLMENT_TABLE, ORDER_TABLE, USER_TABLE
|
|
||||||
|
|
||||||
logger = Logger(__name__)
|
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
|
||||||
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
|
||||||
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
|
||||||
|
|
||||||
|
|
||||||
@event_source(data_class=EventBridgeEvent)
|
|
||||||
@logger.inject_lambda_context
|
|
||||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|
||||||
"""Remove slots if the tenant has a `metadata#billing_policy` and
|
|
||||||
the total is greater than zero."""
|
|
||||||
new_image = event.detail['new_image']
|
|
||||||
data = order_layer.get_item(KeyPair(new_image['id'], '0'))
|
|
||||||
org_id = data.get('tenant_id')
|
|
||||||
|
|
||||||
if not org_id:
|
|
||||||
return False
|
|
||||||
|
|
||||||
policy = user_layer.collection.get_item(
|
|
||||||
KeyPair(pk=org_id, sk='metadata#billing_policy'),
|
|
||||||
raise_on_error=False,
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Skip if billing policy is missing or order is less than or equal to zero
|
|
||||||
if not policy or data['total'] <= 0:
|
|
||||||
logger.info('Missing billing policy')
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.info(f'Billing policy from Org ID "{org_id}" found', policy=policy)
|
|
||||||
|
|
||||||
result = enrollment_layer.collection.query(
|
|
||||||
KeyPair(
|
|
||||||
f'vacancies#{org_id}',
|
|
||||||
new_image['id'],
|
|
||||||
),
|
|
||||||
limit=150,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
'Slots found',
|
|
||||||
total_items=len(result['items']),
|
|
||||||
slots=result['items'],
|
|
||||||
)
|
|
||||||
|
|
||||||
with enrollment_layer.batch_writer() as batch:
|
|
||||||
for pair in result['items']:
|
|
||||||
batch.delete_item(
|
|
||||||
Key={
|
|
||||||
'id': {'S': pair['id']},
|
|
||||||
'sk': {'S': pair['sk']},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info('Deleted all slots')
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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 layercake.dateutils import now
|
|
||||||
from layercake.dynamodb import (
|
|
||||||
DynamoDBPersistenceLayer,
|
|
||||||
KeyPair,
|
|
||||||
)
|
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
|
||||||
from config import ORDER_TABLE
|
|
||||||
|
|
||||||
logger = Logger(__name__)
|
|
||||||
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
|
||||||
|
|
||||||
|
|
||||||
@event_source(data_class=EventBridgeEvent)
|
|
||||||
@logger.inject_lambda_context
|
|
||||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|
||||||
"""Set to `PAID` if the status is `PENDING` and the total is zero."""
|
|
||||||
new_image = event.detail['new_image']
|
|
||||||
now_ = now()
|
|
||||||
|
|
||||||
with order_layer.transact_writer() as transact:
|
|
||||||
transact.update(
|
|
||||||
key=KeyPair(new_image['id'], '0'),
|
|
||||||
update_expr='SET #status = :status, updated_at = :updated_at',
|
|
||||||
expr_attr_names={
|
|
||||||
'#status': 'status',
|
|
||||||
},
|
|
||||||
expr_attr_values={
|
|
||||||
':status': 'PAID',
|
|
||||||
':updated_at': now_,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
transact.put(
|
|
||||||
item={
|
|
||||||
'id': new_image['id'],
|
|
||||||
'sk': 'paid_at',
|
|
||||||
'paid_at': now_,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
@@ -280,10 +280,10 @@ Resources:
|
|||||||
org_id:
|
org_id:
|
||||||
- exists: true
|
- exists: true
|
||||||
|
|
||||||
EventRunSelfDestructionFunction:
|
EventRunAutoCleanupFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.run_self_destruction.lambda_handler
|
Handler: events.run_auto_cleanup.lambda_handler
|
||||||
Timeout: 30
|
Timeout: 30
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
@@ -299,7 +299,9 @@ Resources:
|
|||||||
detail-type: [EXPIRE]
|
detail-type: [EXPIRE]
|
||||||
detail:
|
detail:
|
||||||
keys:
|
keys:
|
||||||
sk: ['SCHEDULED#SELF_DESTRUCTION']
|
sk:
|
||||||
|
- SCHEDULED#AUTO_CLEANUP
|
||||||
|
- SCHEDULED#SELF_DESTRUCTION
|
||||||
|
|
||||||
# DEPRECATED
|
# DEPRECATED
|
||||||
EventAppendOrgIdFunction:
|
EventAppendOrgIdFunction:
|
||||||
@@ -325,7 +327,6 @@ Resources:
|
|||||||
sk: ['0']
|
sk: ['0']
|
||||||
cnpj:
|
cnpj:
|
||||||
- exists: true
|
- exists: true
|
||||||
# Post-migration: rename `tenant_id` to `org_id`
|
|
||||||
tenant_id:
|
tenant_id:
|
||||||
- exists: false
|
- exists: false
|
||||||
|
|
||||||
@@ -382,55 +383,6 @@ Resources:
|
|||||||
- exists: true
|
- exists: true
|
||||||
status: [CANCELED, EXPIRED]
|
status: [CANCELED, EXPIRED]
|
||||||
|
|
||||||
EventStopgapSetAsPaidFunction:
|
|
||||||
Type: AWS::Serverless::Function
|
|
||||||
Properties:
|
|
||||||
Handler: events.stopgap.set_as_paid.lambda_handler
|
|
||||||
LoggingConfig:
|
|
||||||
LogGroup: !Ref EventLog
|
|
||||||
Policies:
|
|
||||||
- DynamoDBWritePolicy:
|
|
||||||
TableName: !Ref OrderTable
|
|
||||||
Events:
|
|
||||||
Event:
|
|
||||||
Type: EventBridgeRule
|
|
||||||
Properties:
|
|
||||||
Pattern:
|
|
||||||
resources: [!Ref OrderTable]
|
|
||||||
detail-type: [INSERT]
|
|
||||||
detail:
|
|
||||||
new_image:
|
|
||||||
sk: ['0']
|
|
||||||
cnpj:
|
|
||||||
- exists: true
|
|
||||||
total: [0]
|
|
||||||
status: [CREATING, PENDING]
|
|
||||||
payment_method: [MANUAL]
|
|
||||||
|
|
||||||
EventStopgapRemoveSlotsFunction:
|
|
||||||
Type: AWS::Serverless::Function
|
|
||||||
Properties:
|
|
||||||
Handler: events.stopgap.remove_slots.lambda_handler
|
|
||||||
LoggingConfig:
|
|
||||||
LogGroup: !Ref EventLog
|
|
||||||
Policies:
|
|
||||||
- DynamoDBReadPolicy:
|
|
||||||
TableName: !Ref UserTable
|
|
||||||
- DynamoDBReadPolicy:
|
|
||||||
TableName: !Ref OrderTable
|
|
||||||
- DynamoDBCrudPolicy:
|
|
||||||
TableName: !Ref EnrollmentTable
|
|
||||||
Events:
|
|
||||||
DynamoDBEvent:
|
|
||||||
Type: EventBridgeRule
|
|
||||||
Properties:
|
|
||||||
Pattern:
|
|
||||||
resources: [!Ref OrderTable]
|
|
||||||
detail:
|
|
||||||
new_image:
|
|
||||||
sk: [generated_items]
|
|
||||||
status: [SUCCESS]
|
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
HttpApiUrl:
|
HttpApiUrl:
|
||||||
Description: URL of your API endpoint
|
Description: URL of your API endpoint
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
from layercake.dynamodb import PartitionKey
|
|
||||||
|
|
||||||
import events.stopgap.remove_slots as app
|
|
||||||
|
|
||||||
from ...conftest import LambdaContext
|
|
||||||
|
|
||||||
|
|
||||||
def test_remove_slots(
|
|
||||||
dynamodb_seeds,
|
|
||||||
dynamodb_persistence_layer,
|
|
||||||
lambda_context: LambdaContext,
|
|
||||||
):
|
|
||||||
event = {
|
|
||||||
'detail': {
|
|
||||||
'new_image': {
|
|
||||||
'id': '9omWNKymwU5U4aeun6mWzZ',
|
|
||||||
'sk': 'generated_items',
|
|
||||||
'create_date': '2024-07-23T20:43:37.303418-03:00',
|
|
||||||
'status': 'SUCCESS',
|
|
||||||
'scope': 'MILTI_USER',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
|
||||||
|
|
||||||
result = dynamodb_persistence_layer.collection.query(
|
|
||||||
PartitionKey('vacancies#cJtK9SsnJhKPyxESe7g3DG')
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(result['items']) == 0
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
|
||||||
|
|
||||||
import events.stopgap.set_as_paid as app
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_as_paid(
|
|
||||||
dynamodb_seeds,
|
|
||||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
|
||||||
lambda_context: LambdaContext,
|
|
||||||
):
|
|
||||||
event = {
|
|
||||||
'detail': {
|
|
||||||
'new_image': {
|
|
||||||
'id': '9omWNKymwU5U4aeun6mWzZ',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
|
||||||
|
|
||||||
doc = dynamodb_persistence_layer.get_item(
|
|
||||||
key=KeyPair('9omWNKymwU5U4aeun6mWzZ', '0'),
|
|
||||||
)
|
|
||||||
assert doc['status'] == 'PAID'
|
|
||||||
Reference in New Issue
Block a user