···1+package database
2+3+import (
4+ "database/sql"
5+ "fmt"
6+ "log/slog"
7+8+ atshorter "tangled.sh/willdot.net/at-shorter-url"
9+)
10+11+func createURLsTable(db *sql.DB) error {
12+ createURLsTableSQL := `CREATE TABLE IF NOT EXISTS urls (
13+ "id" TEXT NOT NULL PRIMARY KEY,
14+ "url" TEXT NOT NULL,
15+ "did" TEXT NOT NULL,
16+ "createdAt" integer
17+ );`
18+19+ slog.Info("Create urls table...")
20+ statement, err := db.Prepare(createURLsTableSQL)
21+ if err != nil {
22+ return fmt.Errorf("prepare DB statement to create urls table: %w", err)
23+ }
24+ _, err = statement.Exec()
25+ if err != nil {
26+ return fmt.Errorf("exec sql statement to create urls table: %w", err)
27+ }
28+ slog.Info("status urls created")
29+30+ return nil
31+}
32+33+func (d *DB) CreateURL(id, url, did string, createdAt int64) error {
34+ sql := `INSERT INTO urls (id, url, did, createdAt) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING;`
35+ _, err := d.db.Exec(sql, id, url, did, createdAt)
36+ if err != nil {
37+ // TODO: catch already exists
38+ return fmt.Errorf("exec insert url: %w", err)
39+ }
40+41+ return nil
42+}
43+44+func (d *DB) GetURLs(did string) ([]atshorter.ShortURL, error) {
45+ sql := "SELECT id, url, did FROM urls WHERE did = ?;"
46+ rows, err := d.db.Query(sql, did)
47+ if err != nil {
48+ return nil, fmt.Errorf("run query to get URLS': %w", err)
49+ }
50+ defer rows.Close()
51+52+ var results []atshorter.ShortURL
53+ for rows.Next() {
54+ var shortURL atshorter.ShortURL
55+ if err := rows.Scan(&shortURL.ID, &shortURL.URL, &shortURL.Did); err != nil {
56+ return nil, fmt.Errorf("scan row: %w", err)
57+ }
58+59+ results = append(results, shortURL)
60+ }
61+ return results, nil
62+}
63+64+func (d *DB) GetURLByID(id string) (atshorter.ShortURL, error) {
65+ sql := "SELECT id, url, did FROM urls WHERE id = ?;"
66+ rows, err := d.db.Query(sql, id)
67+ if err != nil {
68+ return atshorter.ShortURL{}, fmt.Errorf("run query to get URL by id': %w", err)
69+ }
70+ defer rows.Close()
71+72+ var result atshorter.ShortURL
73+ for rows.Next() {
74+ if err := rows.Scan(&result.ID, &result.URL, &result.Did); err != nil {
75+ return atshorter.ShortURL{}, fmt.Errorf("scan row: %w", err)
76+ }
77+ return result, nil
78+ }
79+ return atshorter.ShortURL{}, atshorter.ErrorNotFound
80+}
81+82+func (s *DB) DeleteURL(id, did string) error {
83+ sql := "DELETE FROM urls WHERE id = ? AND did = ?;"
84+ _, err := s.db.Exec(sql, id, did)
85+ if err != nil {
86+ return fmt.Errorf("exec delete URL by id and DID: %w", err)
87+ }
88+ return nil
89+}
+5
example.env
···00000
···1+PRIVATEJWKS="a generated JWKS key"
2+SESSION_KEY="some random secret"
3+HOST="the host of the service such as https://my-url-shortner.com"
4+DATABASE_PATH="./"
5+JS_SERVER_ADDR="set to a different Jetstream instance"