···11+# Agent Instructions
22+33+This is a Go web application template for exe.dev.
44+55+See README.md for details on the structure and components.
+10
Makefile
···11+.PHONY: build clean stop start restart test
22+33+build:
44+ go build -o stayintouch ./cmd/srv
55+66+clean:
77+ rm -f stayintouch
88+99+test:
1010+ go test ./...
+57
README.md
···11+# Go Shelley Template
22+33+This is a starter template for building Go web applications on exe.dev. It demonstrates end-to-end usage including HTTP handlers, authentication, database integration, and deployment.
44+55+Use this as a foundation to build your own service.
66+77+## Building and Running
88+99+Build with `make build`, then run `./srv`. The server listens on port 8000 by default.
1010+1111+## Running as a systemd service
1212+1313+To run the server as a systemd service:
1414+1515+```bash
1616+# Install the service file
1717+sudo cp srv.service /etc/systemd/system/srv.service
1818+1919+# Reload systemd and enable the service
2020+sudo systemctl daemon-reload
2121+sudo systemctl enable srv.service
2222+2323+# Start the service
2424+sudo systemctl start srv
2525+2626+# Check status
2727+systemctl status srv
2828+2929+# View logs
3030+journalctl -u srv -f
3131+```
3232+3333+To restart after code changes:
3434+3535+```bash
3636+make build
3737+sudo systemctl restart srv
3838+```
3939+4040+## Authorization
4141+4242+exe.dev provides authorization headers and login/logout links
4343+that this template uses.
4444+4545+When proxied through exed, requests will include `X-ExeDev-UserID` and
4646+`X-ExeDev-Email` if the user is authenticated via exe.dev.
4747+4848+## Database
4949+5050+This template uses sqlite (`db.sqlite3`). SQL queries are managed with sqlc.
5151+5252+## Code layout
5353+5454+- `cmd/srv`: main package (binary entrypoint)
5555+- `srv`: HTTP server logic (handlers)
5656+- `srv/templates`: Go HTML templates
5757+- `db`: SQLite open + migrations (001-base.sql)
···11+-- Base schema
22+--
33+-- Migrations tracking table
44+CREATE TABLE IF NOT EXISTS migrations (
55+ migration_number INTEGER PRIMARY KEY,
66+ migration_name TEXT NOT NULL,
77+ executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
88+);
99+1010+-- People to keep in touch with
1111+CREATE TABLE IF NOT EXISTS contacts (
1212+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1313+ user_id TEXT NOT NULL,
1414+ name TEXT NOT NULL,
1515+ frequency_days INTEGER NOT NULL DEFAULT 30,
1616+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
1717+);
1818+1919+CREATE INDEX IF NOT EXISTS idx_contacts_user_id ON contacts(user_id);
2020+2121+-- Interactions/catch-ups with contacts
2222+CREATE TABLE IF NOT EXISTS interactions (
2323+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2424+ contact_id INTEGER NOT NULL,
2525+ notes TEXT,
2626+ interacted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
2727+ FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE
2828+);
2929+3030+CREATE INDEX IF NOT EXISTS idx_interactions_contact_id ON interactions(contact_id);
3131+3232+-- Record execution of this migration
3333+INSERT OR IGNORE INTO migrations (migration_number, migration_name)
3434+VALUES (001, '001-base');
+32
db/queries/contacts.sql
···11+-- name: CreateContact :one
22+INSERT INTO contacts (user_id, name, frequency_days, created_at)
33+VALUES (?, ?, ?, ?)
44+RETURNING *;
55+66+-- name: GetContactsByUser :many
77+SELECT
88+ c.*,
99+ (SELECT MAX(interacted_at) FROM interactions WHERE contact_id = c.id) as last_interaction
1010+FROM contacts c
1111+WHERE c.user_id = ?
1212+ORDER BY c.name;
1313+1414+-- name: GetContact :one
1515+SELECT * FROM contacts WHERE id = ? AND user_id = ?;
1616+1717+-- name: UpdateContact :exec
1818+UPDATE contacts SET name = ?, frequency_days = ? WHERE id = ? AND user_id = ?;
1919+2020+-- name: DeleteContact :exec
2121+DELETE FROM contacts WHERE id = ? AND user_id = ?;
2222+2323+-- name: CreateInteraction :one
2424+INSERT INTO interactions (contact_id, notes, interacted_at)
2525+VALUES (?, ?, ?)
2626+RETURNING *;
2727+2828+-- name: GetInteractionsByContact :many
2929+SELECT * FROM interactions WHERE contact_id = ? ORDER BY interacted_at DESC;
3030+3131+-- name: DeleteInteraction :exec
3232+DELETE FROM interactions WHERE id = ?;