package db import ( "database/sql" "errors" "time" ) // ErrNotFound is returned when a document does not exist. var ErrNotFound = errors.New("db: not found") // Document represents a queued document. type Document struct { ID int64 `json:"id"` Slug string `json:"slug"` Title string `json:"title"` Body string `json:"body"` PushedBy string `json:"pushed_by"` PushedAt string `json:"pushed_at"` Read bool `json:"read"` } // ListDocuments returns all documents ordered by most recently pushed. func (d *DB) ListDocuments() ([]Document, error) { rows, err := d.Query(`SELECT id, slug, title, body, pushed_by, pushed_at, read FROM documents ORDER BY pushed_at DESC`) if err != nil { return nil, err } defer rows.Close() var docs []Document for rows.Next() { var doc Document if err := rows.Scan(&doc.ID, &doc.Slug, &doc.Title, &doc.Body, &doc.PushedBy, &doc.PushedAt, &doc.Read); err != nil { return nil, err } docs = append(docs, doc) } return docs, rows.Err() } // GetDocument returns a single document by slug. func (d *DB) GetDocument(slug string) (*Document, error) { var doc Document err := d.QueryRow( `SELECT id, slug, title, body, pushed_by, pushed_at, read FROM documents WHERE slug = ?`, slug, ).Scan(&doc.ID, &doc.Slug, &doc.Title, &doc.Body, &doc.PushedBy, &doc.PushedAt, &doc.Read) if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, err } return &doc, nil } // PutDocument creates or updates a document by slug (upsert). func (d *DB) PutDocument(slug, title, body, pushedBy string) (*Document, error) { now := time.Now().UTC().Format(time.RFC3339) _, err := d.Exec(` INSERT INTO documents (slug, title, body, pushed_by, pushed_at, read) VALUES (?, ?, ?, ?, ?, 0) ON CONFLICT(slug) DO UPDATE SET title = excluded.title, body = excluded.body, pushed_by = excluded.pushed_by, pushed_at = excluded.pushed_at, read = 0`, slug, title, body, pushedBy, now, ) if err != nil { return nil, err } return d.GetDocument(slug) } // DeleteDocument removes a document by slug. func (d *DB) DeleteDocument(slug string) error { res, err := d.Exec(`DELETE FROM documents WHERE slug = ?`, slug) if err != nil { return err } n, err := res.RowsAffected() if err != nil { return err } if n == 0 { return ErrNotFound } return nil } // MarkRead sets the read flag on a document. func (d *DB) MarkRead(slug string) (*Document, error) { return d.setRead(slug, true) } // MarkUnread clears the read flag on a document. func (d *DB) MarkUnread(slug string) (*Document, error) { return d.setRead(slug, false) } func (d *DB) setRead(slug string, read bool) (*Document, error) { val := 0 if read { val = 1 } res, err := d.Exec(`UPDATE documents SET read = ? WHERE slug = ?`, val, slug) if err != nil { return nil, err } n, err := res.RowsAffected() if err != nil { return nil, err } if n == 0 { return nil, ErrNotFound } return d.GetDocument(slug) }