Skip to content

Commit 1abcc8d

Browse files
committed
Address review comments
1 parent 5840b3c commit 1abcc8d

7 files changed

Lines changed: 70 additions & 23 deletions

File tree

File renamed without changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"back_left": {
3+
"qr_left": 222,
4+
"qr_top": 414,
5+
"qr_size": 322,
6+
"code_center_x": 383,
7+
"code_center_y": 801,
8+
"code_font_size": 58
9+
}
10+
}

app/backend/src/couchers/migrations/versions/0141_add_mypostcard_job_id.py renamed to app/backend/src/couchers/migrations/versions/0142_add_mypostcard_job_id.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Add mypostcard_job_id and rename country to country_code in postal_verification_attempts
22
3-
Revision ID: 0141
4-
Revises: 0140
3+
Revision ID: 0142
4+
Revises: 0141
55
Create Date: 2026-03-18 12:00:00.000000
66
77
"""
@@ -10,8 +10,8 @@
1010
from alembic import op
1111

1212
# revision identifiers, used by Alembic.
13-
revision = "0141"
14-
down_revision = "0140"
13+
revision = "0142"
14+
down_revision = "0141"
1515
branch_labels = None
1616
depends_on = None
1717

app/backend/src/couchers/postal/my_postcard.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,53 @@
1010

1111
from couchers import urls
1212
from couchers.config import config
13-
from couchers.resources import get_postcard_back_template, get_postcard_font, get_postcard_front_image
13+
from couchers.resources import (
14+
get_postcard_back_left_template,
15+
get_postcard_font,
16+
get_postcard_front_image,
17+
get_postcard_metadata,
18+
)
1419

1520
logger = logging.getLogger(__name__)
1621

1722
API_BASE = "https://www.mypostcard.com/api/v1"
1823

1924

20-
def _generate_back_left_side(verification_code: str) -> bytes:
25+
def _generate_back_left_side_png(verification_code: str) -> bytes:
2126
"""
2227
Generates the back left side image (780x1016 px PNG at 300 DPI).
2328
24-
Overlays a QR code and verification code onto the postcard-back.png template.
29+
Overlays a QR code and verification code onto the postcard-back-left.png template.
2530
"""
31+
metadata = get_postcard_metadata()["back_left"]
32+
2633
# Load template
27-
template_bytes = get_postcard_back_template()
34+
template_bytes = get_postcard_back_left_template()
2835
img = Image.open(io.BytesIO(template_bytes)).convert("RGBA")
2936
draw = ImageDraw.Draw(img)
3037

31-
# QR code position: exact coordinates in image are (227, 419, 539, 731), extended by 5px in each direction
32-
qr_left, qr_top, qr_size = 222, 414, 322
33-
3438
# Generate QR code
3539
qr = qrcode.QRCode(box_size=10, border=0)
3640
qr.add_data(urls.postal_verification_link(code=verification_code))
3741
qr.make(fit=True)
3842
qr_img: Image.Image = qr.make_image(fill_color="black", back_color="white").get_image().convert("RGBA")
3943

4044
# Size and paste the QR code
45+
# QR code position: exact coordinates in image are (227, 419, 539, 731), extended by 5px in each direction
46+
qr_size = metadata["qr_size"]
4147
qr_img = qr_img.resize((qr_size, qr_size), Image.Resampling.NEAREST)
42-
img.paste(qr_img, (qr_left, qr_top))
48+
img.paste(qr_img, (metadata["qr_left"], metadata["qr_top"]))
4349

4450
# Verification code text center: box in image is (x=251, y=761, w=264, h=80), center is (383, 801)
45-
code_center_x, code_center_y = 383, 801
46-
47-
font = ImageFont.truetype(io.BytesIO(get_postcard_font()), 58)
48-
49-
draw.text((code_center_x, code_center_y), verification_code, fill=(255, 255, 255), font=font, anchor="mm")
51+
font = ImageFont.truetype(io.BytesIO(get_postcard_font()), metadata["code_font_size"])
52+
53+
draw.text(
54+
(metadata["code_center_x"], metadata["code_center_y"]),
55+
verification_code,
56+
fill=(255, 255, 255),
57+
font=font,
58+
anchor="mm",
59+
)
5060

5161
buf = io.BytesIO()
5262
img.save(buf, format="PNG")
@@ -156,7 +166,7 @@ def send_postcard(
156166
recipient["state"] = state
157167

158168
result = _place_order(
159-
_authenticate(), recipient, get_postcard_front_image(), _generate_back_left_side(verification_code)
169+
_authenticate(), recipient, get_postcard_front_image(), _generate_back_left_side_png(verification_code)
160170
)
161171
logger.info(f"MyPostcard order placed successfully: {result}")
162172
return int(result["job_id"])

app/backend/src/couchers/resources.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,19 @@ def get_postcard_font() -> bytes:
121121

122122

123123
@functools.cache
124-
def get_postcard_back_template() -> bytes:
124+
def get_postcard_metadata() -> dict[str, Any]:
125125
"""
126-
Returns the back side template image for the postcard as PNG bytes.
126+
Returns the postcard metadata (coordinates, sizes, etc.) from postcard-metadata.json.
127127
"""
128-
return (resources_folder / "postcard-back.png").read_bytes()
128+
return cast(dict[str, Any], json.loads((resources_folder / "postcard-metadata.json").read_text()))
129+
130+
131+
@functools.cache
132+
def get_postcard_back_left_template() -> bytes:
133+
"""
134+
Returns the back left side template image for the postcard as PNG bytes.
135+
"""
136+
return (resources_folder / "postcard-back-left.png").read_bytes()
129137

130138

131139
def copy_resources_to_database(session: Session) -> None:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
* New verification page
2+
3+
# Verification
4+
5+
Help keep our community safe by verifying important details about yourself.
6+
7+
8+
9+
How verification data is used
10+
11+
Verification data is used for regular anti abuse work... such as making sure folks don't create multiple accounts. Additionally, we may disclose verification information to aid in an investigation of serious wrongdoing ... or if legally compelled to do so.
12+
13+
14+
15+
16+
now let's also implement a background job that makes sure in the last week there are no postcard jobs at mypostcard that are not tied to a verification attempt. run it once every 24hr.
17+
18+
19+
let's add metrics to postcard sending

app/backend/src/tests/test_postal_verification.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from couchers.jobs.worker import process_job
1919
from couchers.models import User
2020
from couchers.models.postal_verification import PostalVerificationAttempt
21-
from couchers.postal.my_postcard import _generate_back_left_side
21+
from couchers.postal.my_postcard import _generate_back_left_side_png
2222
from couchers.proto import postal_verification_pb2
2323
from couchers.resources import get_postcard_front_image
2424
from couchers.utils import now
@@ -699,7 +699,7 @@ def test_generate_postcard_images():
699699
"""
700700
code = "ABC123"
701701
front = get_postcard_front_image()
702-
back = _generate_back_left_side(code)
702+
back = _generate_back_left_side_png(code)
703703

704704
assert len(front) > 0
705705
assert len(back) > 0

0 commit comments

Comments
 (0)