A URL shortener service that uses ATProto to allow self hosting and ensuring the user owns their data
1package database
2
3import (
4 "database/sql"
5 "fmt"
6 "log/slog"
7
8 atshorter "tangled.sh/willdot.net/at-shorter-url"
9)
10
11func 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
33func (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
44func (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
64func (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
82func (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}