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 "originHost" TEXT NOT NULL, 17 "createdAt" integer 18 );` 19 20 slog.Info("Create urls table...") 21 statement, err := db.Prepare(createURLsTableSQL) 22 if err != nil { 23 return fmt.Errorf("prepare DB statement to create urls table: %w", err) 24 } 25 _, err = statement.Exec() 26 if err != nil { 27 return fmt.Errorf("exec sql statement to create urls table: %w", err) 28 } 29 slog.Info("status urls created") 30 31 return nil 32} 33 34func (d *DB) CreateURL(id, url, did, originHost string, createdAt int64) error { 35 sql := `INSERT INTO urls (id, url, did, originHost, createdAt) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING;` 36 _, err := d.db.Exec(sql, id, url, did, originHost, createdAt) 37 if err != nil { 38 // TODO: catch already exists 39 return fmt.Errorf("exec insert url: %w", err) 40 } 41 42 return nil 43} 44 45func (d *DB) GetURLs(did string) ([]atshorter.ShortURL, error) { 46 sql := "SELECT id, url, did, originHost FROM urls WHERE did = ?;" 47 rows, err := d.db.Query(sql, did) 48 if err != nil { 49 return nil, fmt.Errorf("run query to get URLS': %w", err) 50 } 51 defer rows.Close() 52 53 var results []atshorter.ShortURL 54 for rows.Next() { 55 var shortURL atshorter.ShortURL 56 if err := rows.Scan(&shortURL.ID, &shortURL.URL, &shortURL.Did, &shortURL.OriginHost); err != nil { 57 return nil, fmt.Errorf("scan row: %w", err) 58 } 59 60 results = append(results, shortURL) 61 } 62 return results, nil 63} 64 65func (d *DB) GetURLByID(id string) (atshorter.ShortURL, error) { 66 sql := "SELECT id, url, did, originHost FROM urls WHERE id = ?;" 67 rows, err := d.db.Query(sql, id) 68 if err != nil { 69 return atshorter.ShortURL{}, fmt.Errorf("run query to get URL by id': %w", err) 70 } 71 defer rows.Close() 72 73 var result atshorter.ShortURL 74 for rows.Next() { 75 if err := rows.Scan(&result.ID, &result.URL, &result.Did, &result.OriginHost); err != nil { 76 return atshorter.ShortURL{}, fmt.Errorf("scan row: %w", err) 77 } 78 return result, nil 79 } 80 return atshorter.ShortURL{}, atshorter.ErrorNotFound 81} 82 83func (s *DB) DeleteURL(id, did string) error { 84 sql := "DELETE FROM urls WHERE id = ? AND did = ?;" 85 _, err := s.db.Exec(sql, id, did) 86 if err != nil { 87 return fmt.Errorf("exec delete URL by id and DID: %w", err) 88 } 89 return nil 90}