Bookmark Store
CRUD operations with database/sql and lib/pq.
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
var ErrNotFound = errors.New("bookmark not found")
type Bookmark struct {
ID int `json:"id"`
URL string `json:"url"`
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
}
type BookmarkStore struct {
db *sql.DB
}
func NewBookmarkStore(db *sql.DB) *BookmarkStore {
return &BookmarkStore{db: db}
}
func (s *BookmarkStore) Create(ctx context.Context, url, title string) (Bookmark, error) {
var b Bookmark
err := s.db.QueryRowContext(ctx,
"INSERT INTO bookmarks (url, title) VALUES ($1, $2) RETURNING id, url, title, created_at",
url, title,
).Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt)
if err != nil {
return Bookmark{}, fmt.Errorf("create bookmark: %w", err)
}
return b, nil
}
func (s *BookmarkStore) GetByID(ctx context.Context, id int) (Bookmark, error) {
var b Bookmark
err := s.db.QueryRowContext(ctx,
"SELECT id, url, title, created_at FROM bookmarks WHERE id = $1", id,
).Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt)
if errors.Is(err, sql.ErrNoRows) {
return Bookmark{}, ErrNotFound
}
if err != nil {
return Bookmark{}, fmt.Errorf("get bookmark: %w", err)
}
return b, nil
}
func (s *BookmarkStore) List(ctx context.Context) ([]Bookmark, error) {
rows, err := s.db.QueryContext(ctx,
"SELECT id, url, title, created_at FROM bookmarks ORDER BY created_at DESC",
)
if err != nil {
return nil, fmt.Errorf("list bookmarks: %w", err)
}
defer rows.Close()
var bookmarks []Bookmark
for rows.Next() {
var b Bookmark
if err := rows.Scan(&b.ID, &b.URL, &b.Title, &b.CreatedAt); err != nil {
return nil, fmt.Errorf("scan bookmark: %w", err)
}
bookmarks = append(bookmarks, b)
}
return bookmarks, rows.Err()
}
func (s *BookmarkStore) Delete(ctx context.Context, id int) error {
result, err := s.db.ExecContext(ctx, "DELETE FROM bookmarks WHERE id = $1", id)
if err != nil {
return fmt.Errorf("delete bookmark: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return ErrNotFound
}
return nil
}
func main() {
db, err := sql.Open("postgres", "postgres://bookmarks:localdev@localhost:5432/bookmarks?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
store := NewBookmarkStore(db)
ctx := context.Background()
b, err := store.Create(ctx, "https://go.dev", "Go Homepage")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created: %d — %s\n", b.ID, b.Title)
bookmarks, err := store.List(ctx)
if err != nil {
log.Fatal(err)
}
for _, bm := range bookmarks {
fmt.Printf(" %d: %s (%s)\n", bm.ID, bm.Title, bm.URL)
}
}