from datetime import datetime from http import HTTPStatus from io import BytesIO from typing import Annotated, Any from urllib.parse import urlparse import requests from aws_lambda_powertools.event_handler.api_gateway import Response, Router from aws_lambda_powertools.event_handler.exceptions import ( BadRequestError, NotFoundError, ) from aws_lambda_powertools.event_handler.openapi.params import Body from layercake.dateutils import now from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from pydantic import UUID4, BaseModel from api_gateway import JSONResponse from boto3clients import dynamodb_client, s3_client from config import BUCKET_NAME, COURSE_TABLE, PAPERFORGE_API from form_data import parse router = Router() dyn = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) @router.get('/') def get_course(course_id: str): return dyn.collection.get_item( KeyPair(course_id, '0'), exc_cls=NotFoundError, ) @router.post('//scormset/') def get_scormset(course_id: str, scormset_id: str): return dyn.collection.get_item( KeyPair(course_id, f'SCORMSET#{scormset_id}'), exc_cls=NotFoundError, ) class Cert(BaseModel): exp_interval: int | None = None s3_uri: str | None = None def model_dump(self, **kwargs) -> dict[str, Any]: return super().model_dump(exclude_none=True, **kwargs) class Course(BaseModel): id: UUID4 name: str access_period: int cert: Cert unlisted: bool = False rawfile: bytes | None = None @router.put('/') def put_course(course_id: str): event = router.current_event if not event.decoded_body: raise BadRequestError('Invalid request body') body = parse( event.headers, BytesIO(event.decoded_body.encode()), ) course = Course.model_validate({'id': course_id, 'cert': {}} | body) if course.rawfile: object_key = f'certs/templates/{course_id}.html' course.cert.s3_uri = f's3://{BUCKET_NAME}/{object_key}' s3_client.put_object( Bucket=BUCKET_NAME, Key=object_key, Body=course.rawfile, ContentType='text/html', ) with dyn.transact_writer() as transact: transact.update( key=KeyPair(str(course.id), '0'), update_expr='SET #name = :name, \ access_period = :access_period, \ cert = :cert, \ unlisted = :unlisted, \ updated_at = :updated_at', expr_attr_names={ '#name': 'name', }, expr_attr_values={ ':name': course.name, ':cert': course.cert.model_dump(), ':access_period': course.access_period, ':unlisted': course.unlisted, ':updated_at': now(), }, cond_expr='attribute_exists(sk)', exc_cls=BadRequestError, ) return JSONResponse(HTTPStatus.NO_CONTENT) @router.post('//sample') def sample(course_id: str, s3_uri: Annotated[str, Body(embed=True)]): now_ = now() # Send template URI and data to Paperforge API to generate a PDF r = requests.post( PAPERFORGE_API, json={ 'template_uri': s3_uri, 'args': { 'name': 'Juscelino Kubitschek', 'cpf': '***.810.132-**', 'score': 100, 'started_at': now_.strftime('%d/%m/%Y'), 'completed_at': now_.strftime('%d/%m/%Y'), 'today': _datefmt(now_), 'year': now_.strftime('%Y'), 'expires_at': now_.strftime('%d/%m/%Y'), }, }, ) r.raise_for_status() return Response( body=r.content, content_type='application/pdf', status_code=HTTPStatus.OK, headers={ 'Content-Disposition': f'attachment; filename="{course_id}.pdf"', }, ) @router.post('//template') def template(course_id: str, s3_uri: Annotated[str, Body(embed=True)]): parsed = urlparse(s3_uri) bucket = parsed.netloc object_key = parsed.path.lstrip('/') r = s3_client.get_object(Bucket=bucket, Key=object_key) return Response( body=r['Body'].read(), content_type='text/html', status_code=HTTPStatus.OK, headers={ 'Content-Disposition': f'attachment; filename="{course_id}.html"', }, ) def _datefmt(dt: datetime) -> str: months = [ 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro', ] return f'{dt.day:02d} de {months[dt.month - 1]} de {dt.year}'