···11+package database
22+33+import (
44+ "database/sql"
55+ "fmt"
66+ "log/slog"
77+88+ atshorter "tangled.sh/willdot.net/at-shorter-url"
99+)
1010+1111+func createURLsTable(db *sql.DB) error {
1212+ createURLsTableSQL := `CREATE TABLE IF NOT EXISTS urls (
1313+ "id" TEXT NOT NULL PRIMARY KEY,
1414+ "url" TEXT NOT NULL,
1515+ "did" TEXT NOT NULL,
1616+ "createdAt" integer
1717+ );`
1818+1919+ slog.Info("Create urls table...")
2020+ statement, err := db.Prepare(createURLsTableSQL)
2121+ if err != nil {
2222+ return fmt.Errorf("prepare DB statement to create urls table: %w", err)
2323+ }
2424+ _, err = statement.Exec()
2525+ if err != nil {
2626+ return fmt.Errorf("exec sql statement to create urls table: %w", err)
2727+ }
2828+ slog.Info("status urls created")
2929+3030+ return nil
3131+}
3232+3333+func (d *DB) CreateURL(id, url, did string, createdAt int64) error {
3434+ sql := `INSERT INTO urls (id, url, did, createdAt) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING;`
3535+ _, err := d.db.Exec(sql, id, url, did, createdAt)
3636+ if err != nil {
3737+ // TODO: catch already exists
3838+ return fmt.Errorf("exec insert url: %w", err)
3939+ }
4040+4141+ return nil
4242+}
4343+4444+func (d *DB) GetURLs(did string) ([]atshorter.ShortURL, error) {
4545+ sql := "SELECT id, url, did FROM urls WHERE did = ?;"
4646+ rows, err := d.db.Query(sql, did)
4747+ if err != nil {
4848+ return nil, fmt.Errorf("run query to get URLS': %w", err)
4949+ }
5050+ defer rows.Close()
5151+5252+ var results []atshorter.ShortURL
5353+ for rows.Next() {
5454+ var shortURL atshorter.ShortURL
5555+ if err := rows.Scan(&shortURL.ID, &shortURL.URL, &shortURL.Did); err != nil {
5656+ return nil, fmt.Errorf("scan row: %w", err)
5757+ }
5858+5959+ results = append(results, shortURL)
6060+ }
6161+ return results, nil
6262+}
6363+6464+func (d *DB) GetURLByID(id string) (atshorter.ShortURL, error) {
6565+ sql := "SELECT id, url, did FROM urls WHERE id = ?;"
6666+ rows, err := d.db.Query(sql, id)
6767+ if err != nil {
6868+ return atshorter.ShortURL{}, fmt.Errorf("run query to get URL by id': %w", err)
6969+ }
7070+ defer rows.Close()
7171+7272+ var result atshorter.ShortURL
7373+ for rows.Next() {
7474+ if err := rows.Scan(&result.ID, &result.URL, &result.Did); err != nil {
7575+ return atshorter.ShortURL{}, fmt.Errorf("scan row: %w", err)
7676+ }
7777+ return result, nil
7878+ }
7979+ return atshorter.ShortURL{}, atshorter.ErrorNotFound
8080+}
8181+8282+func (s *DB) DeleteURL(id, did string) error {
8383+ sql := "DELETE FROM urls WHERE id = ? AND did = ?;"
8484+ _, err := s.db.Exec(sql, id, did)
8585+ if err != nil {
8686+ return fmt.Errorf("exec delete URL by id and DID: %w", err)
8787+ }
8888+ return nil
8989+}
+5
example.env
···11+PRIVATEJWKS="a generated JWKS key"
22+SESSION_KEY="some random secret"
33+HOST="the host of the service such as https://my-url-shortner.com"
44+DATABASE_PATH="./"
55+JS_SERVER_ADDR="set to a different Jetstream instance"