Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ default_language_version:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.4.8
rev: v0.13.0
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down
27 changes: 20 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def invalidate_cloudfront_distribution(_, _2):

""".format(t=time.time())

S3_GENERATED_PREFIX = "generated/"

app = aws_cdk.App()


Expand All @@ -66,12 +68,12 @@ def __init__(self, scope, id_, **kwargs):
# protect production stacks from accidental deletion
kwargs["termination_protection"] = self.config.get("TERMINATION_PROTECTION")

assert (
"SECRET_KEY" in self.config
), "Need random SECRET_KEY specified in config.json"
assert (
"CERTIFICATE_ARN" in self.config
), "Need CERTIFICATE_ARN specified in config.json"
assert "SECRET_KEY" in self.config, (
"Need random SECRET_KEY specified in config.json"
)
assert "CERTIFICATE_ARN" in self.config, (
"Need CERTIFICATE_ARN specified in config.json"
)

self.stage = self.config["STAGE"]
self.stackname = f"{id_}-{self.stage}"
Expand Down Expand Up @@ -142,6 +144,13 @@ def __init__(self, scope, id_, **kwargs):
static_website_bucket = s3.Bucket(
self,
"Dominion Divider Generator Site",
removal_policy=aws_cdk.RemovalPolicy.DESTROY,
auto_delete_objects=True,
lifecycle_rules=[
s3.LifecycleRule(
expiration=aws_cdk.Duration.days(1), prefix=S3_GENERATED_PREFIX
)
],
)
monitoring_facade.monitor_s3_bucket(bucket=static_website_bucket)

Expand Down Expand Up @@ -179,10 +188,14 @@ def __init__(self, scope, id_, **kwargs):
"GA_CONFIG": self.config.get("GA_CONFIG", ""),
"LOG_LEVEL": self.config.get("LOG_LEVEL", "INFO"),
"FONT_DIR": self.config.get("FONT_DIR", ""),
"OUTPUT_BUCKET": static_website_bucket.bucket_name,
"OUTPUT_PREFIX": S3_GENERATED_PREFIX,
"OUTPUT_URL_PREFIX": f"{static_website_bucket.s3_url_for_object('generated/')}",
},
timeout=aws_cdk.Duration.seconds(60),
memory_size=self.config.get("LAMBDA_MEMORY_SIZE", 1024),
memory_size=self.config.get("LAMBDA_MEMORY_SIZE", 4096),
)
static_website_bucket.grant_write(flask_app)
monitoring_facade.monitor_lambda_function(lambda_function=flask_app)

api = apig.LambdaRestApi(
Expand Down
8 changes: 4 additions & 4 deletions assets/lambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM public.ecr.aws/lambda/python:3.11-x86_64
FROM public.ecr.aws/lambda/python:3.13-x86_64

COPY --from=ghcr.io/astral-sh/uv:0.7.12 /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.8.17 /uv /uvx /bin/

RUN yum update -y && yum install -y git libicu-devel gcc gcc-c++
RUN dnf update -y && dnf install -y git libicu-devel gcc gcc-c++

# Copy requirements.txt
COPY requirements.txt .
Expand All @@ -11,7 +11,7 @@ COPY requirements.txt .
RUN uv pip install --system -r requirements.txt

# Lots of trouble with newer pyicus on Amazon Linux
RUN uv pip install --system "pyicu<2.9"
RUN uv pip install --system "pyicu"

COPY . .

Expand Down
9 changes: 5 additions & 4 deletions assets/lambda/chitbox_form.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from io import BytesIO

import wtforms.fields as wtf_fields
from wtforms import validators
from flask_wtf import FlaskForm
from flask_wtf.file import FileField as FlaskFileField, FileAllowed
from flask_uploads import IMAGES
from chitboxes.chitboxes import ChitBoxGenerator
from flask_uploads import IMAGES
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed
from flask_wtf.file import FileField as FlaskFileField
from wtforms import validators


class ChitboxForm(FlaskForm):
Expand Down
3 changes: 2 additions & 1 deletion assets/lambda/domdiv_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class DomDivForm(FlaskForm):
"andguilds2ndedition": "and Guilds 2nd Edition",
"2ndedition": "2nd Edition",
"-bigbox2-de": "(Deutsche Big Box v2)",
# next shouldn't be needed anymore, test and take out
"risingsun": "Rising Sun",
}
for choice in expansion_choices:
Expand Down Expand Up @@ -219,7 +220,7 @@ class DomDivForm(FlaskForm):
default=False,
)
expansion_dividers_long_name = wtf_fields.BooleanField(
label=("If Expansion Dividers, use edition " "on expansion dividers names"),
label=("If Expansion Dividers, use edition on expansion dividers names"),
default=False,
)
set_icon = wtf_fields.SelectField(
Expand Down
92 changes: 61 additions & 31 deletions assets/lambda/lambda-handlers.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import base64
import binascii
import functools
import io
import json
import os
import random
import string
import sys

from loguru import logger
import apig_wsgi
import boto3
import domdiv
import domdiv.main
import domdiv.db
from flask import Flask, request, send_file, url_for, jsonify, abort
from flask import render_template
from chitbox_form import ChitboxForm
from domdiv_form import DomDivForm
from flask import (
Flask,
abort,
jsonify,
redirect,
render_template,
request,
send_file,
url_for,
)
from flask_bootstrap import Bootstrap4
from flask_uploads import IMAGES

from domdiv_form import DomDivForm
from loguru import logger
from tuckbox_form import TuckboxForm
from chitbox_form import ChitboxForm

PAGES = {
"dominion_dividers": "Dominion Dividers",
Expand All @@ -38,6 +50,12 @@

apig_wsgi_handler = apig_wsgi.make_lambda_handler(flask_app, binary_support=True)


@functools.lru_cache(maxsize=1)
def get_client(c):
return boto3.client(c)


if os.environ.get("DEBUG"):
apig_wsgi_handler_helper = apig_wsgi_handler

Expand All @@ -53,6 +71,30 @@ def get_pages():
return {url_for(p): n for p, n in PAGES.items()}


def upload_pdf_to_s3(buf, file_type):
"""Upload PDF to S3 and return CloudFront URL"""
s3 = get_client("s3")
tag = "".join(random.choice(string.ascii_letters) for i in range(6))
fname = f"{file_type}_{tag}.pdf"
key = f"{os.environ['OUTPUT_PREFIX'].strip('/')}/{fname}"

s3.upload_fileobj(
buf,
os.environ["OUTPUT_BUCKET"],
key,
ExtraArgs={
"ContentType": "application/pdf",
"ContentDisposition": f'attachment; filename="{fname}"',
},
)

# Generate the CloudFront URL for download
static_url = os.environ["STATIC_WEB_URL"].rstrip("/")
output_prefix = os.environ["OUTPUT_PREFIX"].strip("/")
download_url = f"{static_url}/{output_prefix}/{fname}"
return download_url


@flask_app.route("/", methods=["GET", "POST"])
def dominion_dividers():
logger.info(f"root call, request is {request}, form is {request.form}")
Expand All @@ -68,14 +110,10 @@ def dominion_dividers():
logger.info(f"expansion choices: {domdiv.db.get_expansions()}")
if form.validate_on_submit():
buf = form.generate()
r = send_file(
buf,
mimetype="application/pdf",
as_attachment=True,
download_name="sumpfork_dominion_dividers.pdf",
)
logger.info(f"response: {r}")
return r
download_url = upload_pdf_to_s3(buf, "dominion_dividers")

logger.info(f"redirecting to: {download_url}")
return redirect(download_url)

# setting the default doesn't seem to work, so override here
form.expansions.data = ["dominion2ndEdition"]
Expand Down Expand Up @@ -108,14 +146,10 @@ def tuckboxes():
if form.validate_on_submit():
logger.info(f"tuckbox files: {request.files}")
buf = form.generate(files=request.files)
r = send_file(
buf,
mimetype="application/pdf",
as_attachment=True,
download_name="sumpfork_tuckbox.pdf",
)
logger.info(f"response: {r}")
return r
download_url = upload_pdf_to_s3(buf, "tuckbox")

logger.info(f"redirecting to: {download_url}")
return redirect(download_url)
r = render_template(
"index.html",
pages=PAGES,
Expand All @@ -135,14 +169,10 @@ def chitboxes():
if form.validate_on_submit():
logger.info(f"chitbox files: {request.files}")
buf = form.generate(files=request.files)
r = send_file(
buf,
mimetype="application/pdf",
as_attachment=True,
download_name="sumpfork_chitbox.pdf",
)
logger.info(f"response: {r}")
return r
download_url = upload_pdf_to_s3(buf, "chitbox")

logger.info(f"redirecting to: {download_url}")
return redirect(download_url)
r = render_template(
"index.html",
pages=PAGES,
Expand Down
14 changes: 7 additions & 7 deletions assets/lambda/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in -o requirements.txt
apig-wsgi==2.19.0
apig-wsgi==2.20.0
# via -r requirements.in
blinker==1.9.0
# via flask
bootstrap-flask==2.5.0
# via -r requirements.in
chardet==5.2.0
charset-normalizer==3.4.3
# via reportlab
chitboxes==0.0.1
# via -r requirements.in
click==8.2.1
# via flask
configargparse==1.7.1
# via domdiv
domdiv==4.9.2
domdiv==4.9.3
# via -r requirements.in
flask==3.1.1
flask==3.1.2
# via
# bootstrap-flask
# flask-uploads
Expand All @@ -41,17 +41,17 @@ markupsafe==3.0.2
# jinja2
# werkzeug
# wtforms
numpy==2.2.6
numpy==2.3.3
# via
# chitboxes
# tuckboxes
pillow==11.2.1
pillow==11.3.0
# via
# chitboxes
# domdiv
# reportlab
# tuckboxes
reportlab==4.4.1
reportlab==4.4.3
# via
# chitboxes
# domdiv
Expand Down
11 changes: 6 additions & 5 deletions assets/lambda/tuckbox_form.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from io import BytesIO
import re
from io import BytesIO

from loguru import logger
import wtforms.fields as wtf_fields
from wtforms import validators
from flask_wtf import FlaskForm
from flask_wtf.file import FileField as FlaskFileField, FileAllowed
from flask_uploads import IMAGES
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed
from flask_wtf.file import FileField as FlaskFileField
from loguru import logger
from tuckboxes.tuckboxes import TuckBoxGenerator
from wtforms import validators


class TuckboxForm(FlaskForm):
Expand Down
Loading