Go backend for jyates.dev — four HTTP Lambda functions behind API Gateway plus an S3-triggered content notification function.
- Language: Go 1.26 (CI reads the version from
backend/go.mod) - Runtime: AWS Lambda (ARM64,
provided.al2023) - Database: DynamoDB (KMS-encrypted, on-demand)
- Email: SES v2 (sends from
blog@jyates.dev) - Router: chi (via
aws-lambda-go-api-proxy) - Structure: Handlers → Services → Repositories
| Function | Purpose |
|---|---|
interactions |
Likes and comments (GET/POST) |
contact |
Contact form → SES email |
admin |
Comment moderation (approve/reject/delete) |
authorizer |
Basic Auth for admin endpoints |
notifications |
New-content manifest → confirmed SES topic subscribers |
GET /api/v1/likes?slug=...
POST /api/v1/likes {slug}
GET /api/v1/comments?slug=...
POST /api/v1/comments {slug, content, authorName, authorEmail, website}
POST /api/v1/comments/:id/like {slug}
POST /api/v1/contact {name, email, message, website}
POST /api/v1/subscriptions {email, topics, website}
POST /api/v1/subscriptions/confirm {token}
GET /api/v1/geo
GET /api/v1/visits
POST /api/v1/visits
GET /api/v1/admin/comments?status=...
PUT /api/v1/admin/comments/:id {slug, status}
DELETE /api/v1/admin/comments/:id {slug}
- Honeypot:
websiteis a hidden form field that must be empty; submissions that fill it are rejected (contact returns a fake 200 so bots aren't tipped off). There is no reCAPTCHA. - Likes are deduplicated by the
X-Visitor-Idrequest header (required — requests without it get a 400), not by IP. - Comments require a valid email address and are approved unless
AUTO_APPROVE=false; the admin dashboard can approve or reject them. The CloudFront-generated viewer address is stored for moderation context, and comment creation is limited per IP/day. - Subscriptions use a 48-hour, single-use email confirmation token. Confirmed
addresses and
blog/projectspreferences live in an SES contact list, which also supplies one-click unsubscribe links. Pending token hashes live in DynamoDB.
Single table jyatesdotdev-state with single-table design:
| PK | SK | Purpose |
|---|---|---|
POST#<slug> |
METADATA |
Post metadata (likeCount) |
POST#<slug> |
LIKE#<visitorID> |
Like record (existence = liked) |
POST#<slug> |
COMMENT#<uuid> |
Comment (content, author, status, likeCount) |
COMMENT#<uuid> |
LIKE#<visitorID> |
Comment like tracking |
POST#<slug>#USER#<visitorID> |
LIKE#COMMENT#<uuid> |
User-comment like cross-reference |
SUBSCRIPTION_REQUEST#<tokenHash> |
METADATA |
Pending double-opt-in request (TTL; atomically consumed) |
NOTIFICATION#<commitSHA> |
MANIFEST |
Completed-manifest marker (TTL) |
NOTIFICATION#<commitSHA> |
RECIPIENT#<event>#<emailHash> |
Resumable per-recipient delivery checkpoint (TTL) |
GSI1 (GSI1PK/GSI1SK) indexes comments by status for admin queries (e.g., STATUS#approved, STATUS#pending).
cd backend
go test -short ./...Pushes to main (under backend/**) or manual workflow_dispatch trigger the pipeline:
- Build five Lambda binaries (cross-compiled for
linux/arm64) - Zip and upload to the artifacts S3 bucket under
lambdas/<git-sha>/ - Dispatch
deploy_apitojyatesdotdev-infrawith the artifact locations, andrun_e2etojyatesdotdev-integration
gh workflow run deploy.yml --repo <owner>/jyatesdotdev-api --ref mainThis builds, uploads, and dispatches to infra automatically.
| Type | Name | Description |
|---|---|---|
| Secret | AWS_ROLE_ARN |
GitHub OIDC deploy role ARN |
| Secret | INFRA_REPO_PAT |
PAT to trigger jyatesdotdev-infra / jyatesdotdev-integration dispatches |
| Variable | ARTIFACTS_BUCKET |
S3 bucket for Lambda zips |
| Variable | AWS_REGION |
us-west-2 |