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
6 changes: 6 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ set -e
cd "$(git rev-parse --show-toplevel)"
make lint
make check-content
# Extra Go lint when api/ paths are staged (make lint already runs api-lint if go present)
if git diff --cached --name-only | grep -q '^api/'; then
if command -v go >/dev/null 2>&1; then
make api-lint
fi
fi
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,21 @@ jobs:
- name: Content quality gates
run: make quality
continue-on-error: true

api:
runs-on: ubuntu-latest
defaults:
run:
working-directory: api
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: api/go.mod
cache-dependency-path: api/go.sum
- name: Lint
run: cd .. && make api-lint
- name: Test
run: cd .. && make api-test
- name: Build
run: CGO_ENABLED=0 go build -o /tmp/htlb-api ./cmd/htlb-api
50 changes: 50 additions & 0 deletions .github/workflows/deploy-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: deploy-api
# Lint/test/build htlb-api and rsync binary to dlgrv (api.dlgrv.com).

on:
push:
branches: [main]
paths:
- 'api/**'
- 'deploy/htlb-api.service'
- 'deploy/scripts/deploy-api.sh'
- '.github/workflows/deploy-api.yml'
workflow_dispatch:

concurrency:
group: deploy-api
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: api/go.mod
cache-dependency-path: api/go.sum
- name: Lint
run: make api-lint
- name: Test
run: make api-test
- name: Build linux/amd64
run: make api-build
- name: Setup SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
test -n "$DEPLOY_SSH_KEY"
mkdir -p ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Deploy binary
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
HEALTH_URL: https://api.dlgrv.com/api/htlb/v1/health
run: |
DEPLOY_USER="$DEPLOY_USER" DEPLOY_HOST="$DEPLOY_HOST" HEALTH_URL="$HEALTH_URL" \
bash deploy/scripts/deploy-api.sh deploy
56 changes: 56 additions & 0 deletions .github/workflows/deploy-book.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: deploy-book
# Build static book and rsync to dlgrv (book.dlgrv.com).

on:
push:
branches: [main]
paths:
- 'book/**'
- 'site/**'
- 'forge/site/**'
- 'README*.md'
- 'docs/research/**'
- 'translate/langs.json'
- '.github/workflows/deploy-book.yml'
- 'deploy/scripts/deploy-book.sh'
workflow_dispatch:

concurrency:
group: deploy-book
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
env:
PYTHONPATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install deps
run: |
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
- name: Build + stage
run: |
.venv/bin/python3 forge/site/build_pages.py
.venv/bin/python3 forge/site/pages_artifact.py
test -f .publish/en/index.html
- name: Setup SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
test -n "$DEPLOY_SSH_KEY"
mkdir -p ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Rsync to dlgrv
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
DEPLOY_USER="$DEPLOY_USER" DEPLOY_HOST="$DEPLOY_HOST" bash deploy/scripts/deploy-book.sh
64 changes: 45 additions & 19 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: pages
# Deploy to GitHub Pages only after workflow "test" succeeds on main.
# GitHub Pages is a redirect stub to https://book.dlgrv.com (canonical book host).

on:
workflow_run:
Expand All @@ -24,25 +24,51 @@ jobs:
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-latest
env:
PYTHONPATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
with:
# workflow_run checkout defaults to default branch tip; pin to the
# commit that passed tests when triggered by workflow_run.
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install deps
run: pip install -r requirements-dev.txt
- name: Build site pages
run: python3 forge/site/build_pages.py
- name: Stage Pages artifact
run: python3 forge/site/pages_artifact.py
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
- name: Stage redirect stub
run: |
mkdir -p .publish
cat > .publish/index.html <<'HTML'
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=https://book.dlgrv.com/">
<link rel="canonical" href="https://book.dlgrv.com/">
<title>Moved to book.dlgrv.com</title>
<script>
location.replace(
'https://book.dlgrv.com/'
+ location.pathname.replace(/^\\/HowToLiveBetter\\/?/, '')
+ location.search + location.hash
);
</script>
</head>
<body>
<p>This book now lives at <a href="https://book.dlgrv.com/">book.dlgrv.com</a>.</p>
</body>
</html>
HTML
# Also redirect common locale paths via meta pages
for lang in en ru zh es pt ar id; do
mkdir -p ".publish/$lang"
cat > ".publish/$lang/index.html" <<HTML
<!doctype html>
<meta http-equiv="refresh" content="0; url=https://book.dlgrv.com/$lang/">
<link rel="canonical" href="https://book.dlgrv.com/$lang/">
<script>location.replace('https://book.dlgrv.com/$lang/'+location.search+location.hash)</script>
<p><a href="https://book.dlgrv.com/$lang/">Continue</a></p>
HTML
done
mkdir -p .publish/login
cat > .publish/login/index.html <<'HTML'
<!doctype html>
<meta http-equiv="refresh" content="0; url=https://book.dlgrv.com/login/">
<link rel="canonical" href="https://book.dlgrv.com/login/">
<script>location.replace('https://book.dlgrv.com/login/'+location.search+location.hash)</script>
<p><a href="https://book.dlgrv.com/login/">Continue</a></p>
HTML
- uses: actions/upload-pages-artifact@v3
with:
path: .publish

Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,9 @@ venv/

# archived agent session notes (not part of the live pipeline)
docs/agent-sessions/

# htlb-api local state
api/.local/
api/pb_data/
api/bin/
api/coverage.out
41 changes: 40 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ RUFF = .venv/bin/ruff
DJLINT = .venv/bin/djlint
YAMLLINT = .venv/bin/yamllint

.PHONY: help sync-upstream digest assemble verify verify-all wave repair status lint format test test-integration ci og og-html update-readme hooks check-commit-msg pages-artifact serve web-build quality style lt check-content check-links ebook-deps ebook-test ebook-epub ebook-pdf ebooks
.PHONY: help sync-upstream digest assemble verify verify-all wave repair status lint format test test-integration ci og og-html update-readme hooks check-commit-msg pages-artifact serve web-build quality style lt check-content check-links ebook-deps ebook-test ebook-epub ebook-pdf ebooks api-dev api-test api-lint api-format api-build api-smoke api-vuln api-cover

GOLANGCI_LINT_VERSION := v2.1.6
GOLANGCI_LINT := github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
API_DIR := api

# Locale list must stay in sync with translate/langs.json (registry).
OG_HTML = forge/og/en.html forge/og/ru.html forge/og/es.html forge/og/zh.html forge/og/pt.html forge/og/ar.html forge/og/id.html
Expand All @@ -35,6 +39,10 @@ ci: ## Local CI ≈ GitHub test job (tests+lint+links+content+pages+artifact)
@echo "=== Pages artifact ==="
$(PY) forge/site/pages_artifact.py
@test -f .publish/en/index.html && test -f .publish/README.md && test -d .publish/book
@echo "=== API ==="
$(MAKE) api-lint
$(MAKE) api-test
cd $(API_DIR) && CGO_ENABLED=0 go build -o /tmp/htlb-api-ci ./cmd/htlb-api

hooks: ## Install local git hooks (commit-msg + pre-commit lint/content + pre-push tests)
git config core.hooksPath .githooks
Expand All @@ -59,6 +67,35 @@ quality: ## Content quality (readability + style --book). Usage: make quality [
$(PY) translate/shelf/style_check.py --book --lang $$lang --strict || exit 1; \
done


# --- htlb-api (Go / PocketBase) ---
api-dev: ## Run API locally on :8090
cd $(API_DIR) && HTLB_DEV=1 HTLB_VOTE_SALT=$${HTLB_VOTE_SALT:-dev} HTLB_CORS_ORIGINS=$${HTLB_CORS_ORIGINS:-http://127.0.0.1:8000,http://localhost:8000} go run ./cmd/htlb-api serve --http=127.0.0.1:8090

api-test: ## Go unit/integration tests (CGO_ENABLED=0)
cd $(API_DIR) && CGO_ENABLED=0 go test ./...

api-cover: ## Go coverage report
cd $(API_DIR) && CGO_ENABLED=0 go test ./... -coverprofile=coverage.out
cd $(API_DIR) && go tool cover -func=coverage.out | tail -n 1

api-lint: ## golangci-lint + go mod tidy -diff
cd $(API_DIR) && go mod tidy -diff
cd $(API_DIR) && go run $(GOLANGCI_LINT) run ./...

api-format: ## gofumpt via golangci-lint formatters
cd $(API_DIR) && gofmt -w $$(find . -name '*.go' -not -path './.local/*')

api-build: ## Build linux/amd64 static binary to api/bin/htlb-api
mkdir -p $(API_DIR)/bin
cd $(API_DIR) && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.Version=$$(git rev-parse --short HEAD)" -o bin/htlb-api ./cmd/htlb-api

api-smoke: ## Smoke against ephemeral binary + pb_data
bash $(API_DIR)/scripts/smoke.sh

api-vuln: ## govulncheck
cd $(API_DIR) && go run golang.org/x/vuln/cmd/govulncheck@latest ./...

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

Expand Down Expand Up @@ -190,10 +227,12 @@ lint: ## All code linters (must match CI)
$(DJLINT) site/index.html --lint
$(YAMLLINT) .github/workflows/
shellcheck translate/steps/translate/*.sh
@if command -v go >/dev/null 2>&1; then $(MAKE) api-lint; else echo "skip api-lint (go not on PATH)"; fi

test: ## Run unit tests (excludes integration)
$(PY) -m pytest translate/validate/tests/ translate/llm/tests/ forge/site/tests/ -v --ignore=translate/validate/tests/integration
node --test forge/ebook/book.test.mjs
@if command -v go >/dev/null 2>&1; then $(MAKE) api-test; else echo "skip api-test (go not on PATH)"; fi

ebook-deps: ## Install forge/ebook npm dependencies
npm ci --prefix forge/ebook
Expand Down
22 changes: 22 additions & 0 deletions api/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: "2"
linters:
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
- gosec
- errorlint
- bodyclose
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
formatters:
enable:
- gofumpt
- goimports
45 changes: 45 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# htlb-api

Go backend for HowToLiveBetter interactive features (PocketBase as a library).

## Public API

Base: `https://api.dlgrv.com/api/htlb/v1`

| Method | Path | Auth |
|--------|------|------|
| GET | `/health` | none |
| POST | `/guest/session` | none |
| POST | `/guest/claim-code` | `X-HTLB-Sync` |
| POST | `/guest/claim` | body `{code}` |
| GET/POST | `/useful` | optional / required |
| GET/PUT/DELETE | `/bookmarks` | guest or user |
| GET/PUT | `/reading` | guest or user |
| POST | `/merge` | `Authorization` + `X-HTLB-Sync` |

Guest credential: header `X-HTLB-Sync` only. User JWT: `Authorization: Bearer …`.

OAuth: Google + GitHub only (password auth disabled). Configure via env:

- `HTLB_OAUTH_GOOGLE_CLIENT_ID` / `HTLB_OAUTH_GOOGLE_CLIENT_SECRET`
- `HTLB_OAUTH_GITHUB_CLIENT_ID` / `HTLB_OAUTH_GITHUB_CLIENT_SECRET`
- `HTLB_CORS_ORIGINS` (comma-separated)
- `HTLB_VOTE_SALT`
- `HTLB_DATA_DIR` (default `./pb_data`)

## Local

```bash
cd api
export HTLB_VOTE_SALT=dev
export HTLB_CORS_ORIGINS=http://127.0.0.1:8000
make -C .. api-dev
```

## Build

```bash
CGO_ENABLED=0 go build -o bin/htlb-api ./cmd/htlb-api
```

Attribution: PocketBase (MIT) — see `deploy/THIRD_PARTY.md`.
Loading
Loading