mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-02 00:06:58 +00:00
getCustomerDID returned "" for a FAILED customer.Get exactly as it does for a
customer carrying no user_did. handleSubscriptionChange read that as "not our
customer" and returned nil, so HandleWebhook recorded the event as processed
and answered 200. Stripe never redelivered. A transient Stripe API error
therefore dropped a paid upgrade permanently — the precise "paid but never
received tier" hole 12c55ed was written to close, left open one level down.
It now returns (string, error) so the two cases are distinguishable, and only
the subscription path propagates it. The invoice-failed and dispute handlers
use the DID for logging alone, so a lookup failure there is not worth failing a
webhook over and they ignore it deliberately.
Also fixed: handleSubscriptionChange dereferenced sub.Customer.ID four lines
after an ordering guard that explicitly checks sub.Customer != nil. Confirmed a
real panic, not a theoretical one — the new test panics against the old code.
Five tests, all mutation-verified, and all of them new ground: pkg/billing had
one test file and test/stripe-integration builds its manager with a nil
database, so every `m.db != nil` branch — which is all of the idempotency and
ordering work — was dead there. These use a real database.
The idempotency and ordering tests assert on whether Stripe was CALLED again,
not on row counts. That distinction matters: RecordStripeEvent is an idempotent
upsert, so deleting either guard outright leaves the table looking identical
and a row-count assertion passes. Counting API calls is the only thing that
separates "short-circuited" from "re-applied". My first draft got this wrong
and passed against both mutations.
Makefile: `make test` never ran any of this. The only -tags billing in the file
was a build line, so gate_test.go and checkout_gate_test.go had never executed
in the default target or in CI, and `make lint` never linted the package
either. Both now do; both are clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeaUS5AFPX9gqCahoLRMRh
215 lines
8.8 KiB
Makefile
215 lines
8.8 KiB
Makefile
# ATCR Makefile
|
|
# Build targets for the ATProto Container Registry
|
|
|
|
.PHONY: all build build-appview build-hold build-credential-helper build-oauth-helper \
|
|
build-trixie \
|
|
generate test test-billing test-race test-verbose integration-test stripe-integration-test \
|
|
lint lex-lint clean help install-credential-helper \
|
|
develop develop-detached develop-down dev \
|
|
docker docker-appview docker-hold docker-scanner
|
|
|
|
.DEFAULT_GOAL := help
|
|
|
|
help: ## Show this help message
|
|
@echo "ATCR Build Targets:"
|
|
@echo ""
|
|
@awk 'BEGIN {FS = ":.*##"; printf ""} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-28s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
|
|
|
all: generate build ## Generate assets and build all binaries (default)
|
|
|
|
# Generated asset files
|
|
GENERATED_ASSETS = \
|
|
pkg/appview/public/js/htmx.min.js \
|
|
pkg/appview/public/js/lucide.min.js \
|
|
pkg/appview/licenses/spdx-licenses.json
|
|
|
|
generate: ## Run go generate ./... (always — regenerates cbor_gen, icon sprites, vendor assets)
|
|
@echo "→ Running go generate ./..."
|
|
go generate ./...
|
|
|
|
# File rule: lazily download missing vendor assets for fast incremental local builds.
|
|
# Production builds depend on the phony `generate` target instead so generated code
|
|
# (cbor_gen.go, icon sprites, etc.) is always up to date.
|
|
$(GENERATED_ASSETS):
|
|
@echo "→ Generating vendor assets and code..."
|
|
go generate ./...
|
|
|
|
##@ Build Targets
|
|
|
|
build: build-appview build-hold build-credential-helper ## Build all binaries
|
|
|
|
# Legal page "Last updated" dates come from the git commit date of the page
|
|
# templates. Empty values (e.g., Docker builds without .git) fall back to the
|
|
# hardcoded default in legal.go.
|
|
LEGAL_PKG := atcr.io/pkg/appview/handlers
|
|
PRIVACY_DATE := $(shell git log -1 --format=%cs -- pkg/appview/templates/pages/privacy.html 2>/dev/null)
|
|
TERMS_DATE := $(shell git log -1 --format=%cs -- pkg/appview/templates/pages/terms.html 2>/dev/null)
|
|
APPVIEW_LDFLAGS := -X '$(LEGAL_PKG).privacyLastUpdated=$(PRIVACY_DATE)' -X '$(LEGAL_PKG).termsLastUpdated=$(TERMS_DATE)'
|
|
|
|
build-appview: $(GENERATED_ASSETS) ## Build appview binary only
|
|
@echo "→ Building appview..."
|
|
@mkdir -p bin
|
|
go build -ldflags="$(APPVIEW_LDFLAGS)" -o bin/atcr-appview ./cmd/appview
|
|
|
|
build-hold: $(GENERATED_ASSETS) ## Build hold binary only
|
|
@echo "→ Building hold..."
|
|
@mkdir -p bin
|
|
go build -o bin/atcr-hold ./cmd/hold
|
|
|
|
build-credential-helper: ## Build credential helper only (atcr brand)
|
|
@echo "→ Building credential helper..."
|
|
@mkdir -p bin
|
|
cd cmd/credential-helper/atcr && go build -ldflags="-X main.version=$(shell git describe --tags --always 2>/dev/null || echo dev) -X main.commit=$(shell git rev-parse HEAD 2>/dev/null || echo none)" -o ../../../bin/docker-credential-atcr .
|
|
|
|
build-oauth-helper: ## Build OAuth helper only
|
|
@echo "→ Building OAuth helper..."
|
|
@mkdir -p bin
|
|
go build -o bin/oauth-helper ./cmd/oauth-helper
|
|
|
|
# Trixie cross-build (Debian 13, glibc 2.41) — produces binaries that run on
|
|
# any glibc ≥ 2.41 target, even when the host glibc is newer (e.g. Fedora's
|
|
# 2.43, which otherwise stamps sqrtf@GLIBC_2.43 onto cgo-linked output).
|
|
TRIXIE_BUILDER_IMAGE ?= golang:1-trixie
|
|
|
|
build-trixie: generate ## Build all production binaries (appview, hold, credential-helper, scanner, labeler) for linux/amd64 in a Debian 13 (glibc 2.41) container
|
|
@echo "→ Building in $(TRIXIE_BUILDER_IMAGE) for glibc 2.41 compatibility..."
|
|
@mkdir -p bin
|
|
docker run --rm \
|
|
--user $$(id -u):$$(id -g) \
|
|
-v "$(CURDIR)":/src \
|
|
-w /src \
|
|
-e HOME=/tmp \
|
|
-e GOCACHE=/tmp/.gocache \
|
|
-e GOMODCACHE=/tmp/.gomodcache \
|
|
-e CGO_ENABLED=1 \
|
|
-e GOOS=linux \
|
|
-e GOARCH=amd64 \
|
|
$(TRIXIE_BUILDER_IMAGE) \
|
|
bash -c '\
|
|
set -e && \
|
|
go build -trimpath -tags billing -ldflags="-s -w $(APPVIEW_LDFLAGS)" -o bin/atcr-appview ./cmd/appview && \
|
|
go build -trimpath -ldflags="-s -w" -o bin/atcr-hold ./cmd/hold && \
|
|
(cd cmd/credential-helper/atcr && go build -trimpath -ldflags="-s -w" -o ../../../bin/docker-credential-atcr .) && \
|
|
go build -trimpath -ldflags="-s -w" -o bin/atcr-labeler ./cmd/labeler && \
|
|
cd scanner && go build -trimpath -ldflags="-s -w" -o ../bin/atcr-scanner ./cmd/scanner'
|
|
@echo "✓ Built to bin/ (glibc ≥ 2.41 compatible)"
|
|
|
|
##@ Test Targets
|
|
|
|
test: test-billing ## Run all tests
|
|
@echo "→ Running tests..."
|
|
go test -cover ./...
|
|
|
|
# pkg/billing is behind the `billing` build tag, so `go test ./...` never
|
|
# compiles it, let alone runs it. Its tests covered the money path and had
|
|
# never executed in this target or in CI.
|
|
test-billing: ## Run the billing-tagged tests (skipped by plain `go test ./...`)
|
|
@echo "→ Running billing-tagged tests..."
|
|
go test -tags billing -cover ./pkg/billing/...
|
|
|
|
test-race: ## Run tests with race detector
|
|
@echo "→ Running tests with race detector..."
|
|
go test -race ./...
|
|
|
|
test-verbose: ## Run tests with verbose output
|
|
@echo "→ Running tests with verbose output..."
|
|
go test -v ./...
|
|
|
|
integration-test: ## Run in-process smoke test (no docker, fake PDS + gofakes3 + hold + appview)
|
|
@echo "→ Running integration smoke test..."
|
|
go test -tags=integration -count=1 -race -timeout=120s ./test/integration/...
|
|
|
|
stripe-integration-test: ## Run Stripe sandbox-backed billing tests (needs STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_TEST_PRICE_MONTHLY, STRIPE_TEST_PRICE_YEARLY)
|
|
@echo "→ Running Stripe sandbox integration tests..."
|
|
@echo " Required env: STRIPE_SECRET_KEY (sk_test_...), STRIPE_WEBHOOK_SECRET (whsec_...),"
|
|
@echo " STRIPE_TEST_PRICE_MONTHLY, STRIPE_TEST_PRICE_YEARLY"
|
|
@echo " Optional env: STRIPE_TEST_TIER_NAME (default 'Supporter'),"
|
|
@echo " STRIPE_TEST_EXISTING_CUSTOMER_DID (skips portal search-lag wait)"
|
|
go test -tags="billing stripe_integration" -count=1 -timeout=180s ./test/stripe-integration/...
|
|
|
|
##@ Quality Targets
|
|
|
|
.PHONY: check-golangci-lint
|
|
check-golangci-lint:
|
|
@LINT_PKG=github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; \
|
|
CUR_GO=$$(go version | grep -oE 'go[0-9]+\.[0-9]+' | head -1 | sed 's/^go//'); \
|
|
if ! command -v golangci-lint > /dev/null 2>&1; then \
|
|
echo "→ Installing golangci-lint..."; \
|
|
go install $$LINT_PKG; \
|
|
else \
|
|
LINT_GO=$$(golangci-lint --version 2>&1 | grep -oE 'built with go[0-9]+\.[0-9]+' | head -1 | sed 's/^built with go//'); \
|
|
if [ -n "$$LINT_GO" ] && [ "$$LINT_GO" != "$$CUR_GO" ] && \
|
|
[ "$$(printf '%s\n%s\n' $$LINT_GO $$CUR_GO | sort -V | head -1)" = "$$LINT_GO" ]; then \
|
|
echo "→ golangci-lint built with go$$LINT_GO but project targets go$$CUR_GO — reinstalling..."; \
|
|
go install $$LINT_PKG; \
|
|
fi; \
|
|
fi
|
|
|
|
lint: check-golangci-lint ## Run golangci-lint
|
|
@echo "→ Running golangci-lint..."
|
|
golangci-lint run ./...
|
|
@echo "→ Running golangci-lint (billing tag)..."
|
|
golangci-lint run --build-tags=billing ./pkg/billing/...
|
|
|
|
lex-lint: ## Lint ATProto lexicon schemas
|
|
goat lex lint ./lexicons/
|
|
|
|
##@ Install Targets
|
|
|
|
install-credential-helper: build-credential-helper ## Install credential helper to /usr/local/sbin
|
|
@echo "→ Installing credential helper to /usr/local/sbin..."
|
|
install -m 755 bin/docker-credential-atcr /usr/local/sbin/docker-credential-atcr
|
|
@echo "✓ Installed docker-credential-atcr to /usr/local/sbin/"
|
|
|
|
##@ Development Targets
|
|
|
|
dev: $(GENERATED_ASSETS) ## Run AppView locally with Air hot reload
|
|
@which air > /dev/null || (echo "→ Installing Air..." && go install github.com/air-verse/air@latest)
|
|
air -c .air.toml
|
|
|
|
##@ Docker Targets
|
|
|
|
docker: docker-appview docker-hold docker-scanner ## Build all Docker images
|
|
|
|
docker-appview: ## Build appview Docker image
|
|
@echo "→ Building appview Docker image..."
|
|
docker build -f Dockerfile.appview \
|
|
--build-arg PRIVACY_DATE=$(PRIVACY_DATE) \
|
|
--build-arg TERMS_DATE=$(TERMS_DATE) \
|
|
-t atcr.io/atcr.io/appview:latest .
|
|
|
|
docker-hold: ## Build hold Docker image
|
|
@echo "→ Building hold Docker image..."
|
|
docker build -f Dockerfile.hold -t atcr.io/atcr.io/hold:latest .
|
|
|
|
docker-scanner: ## Build scanner Docker image
|
|
@echo "→ Building scanner Docker image..."
|
|
docker build -f Dockerfile.scanner -t atcr.io/atcr.io/scanner:latest .
|
|
|
|
develop: ## Build and start docker-compose with Air hot reload
|
|
@echo "→ Building Docker images..."
|
|
docker-compose build
|
|
@echo "→ Starting docker-compose with hot reload..."
|
|
docker-compose up
|
|
|
|
develop-detached: ## Build and start docker-compose with hot reload (detached)
|
|
@echo "→ Building Docker images..."
|
|
docker-compose build
|
|
@echo "→ Starting docker-compose with hot reload (detached)..."
|
|
docker-compose up -d
|
|
@echo "✓ Services started in background with hot reload"
|
|
@echo " AppView: http://localhost:5000"
|
|
@echo " Hold: http://localhost:8080"
|
|
|
|
develop-down: ## Stop docker-compose services
|
|
@echo "→ Stopping docker-compose..."
|
|
docker-compose down
|
|
|
|
##@ Utility Targets
|
|
|
|
clean: ## Remove built binaries and generated assets
|
|
@echo "→ Cleaning build artifacts..."
|
|
rm -rf bin/
|
|
rm -f pkg/appview/licenses/spdx-licenses.json
|
|
@echo "✓ Clean complete"
|