main pico / pkg / apps / pipe / db.go
Antonio Mika  ·  2026-10-11
  1package pipe
  2
  3import (
  4	"fmt"
  5	"time"
  6
  7	"github.com/picosh/pico/pkg/db"
  8	"github.com/picosh/pico/pkg/db/postgres"
  9)
 10
 11// DB is the storage pipe uses.
 12type DB interface {
 13	db.AuthDB
 14	FindUserByToken(token string) (*db.User, error)
 15	UpsertPipeMonitor(userID, topic string, dur time.Duration, winEnd *time.Time) error
 16	UpdatePipeMonitorLastPing(userID, topic string, lastPing *time.Time) error
 17	RemovePipeMonitor(userID, topic string) error
 18	FindPipeMonitorByTopic(userID, topic string) (*db.PipeMonitor, error)
 19	FindPipeMonitorsByUser(userID string) ([]*db.PipeMonitor, error)
 20	InsertPipeMonitorHistory(monitorID string, windowDur time.Duration, windowEnd, lastPing *time.Time) error
 21	FindPipeMonitorHistory(monitorID string, from, to time.Time) ([]*db.PipeMonitorHistory, error)
 22}
 23
 24// PsqlDB is pipe's postgres storage: the queries apps share, and the ones
 25// only pipe makes.
 26type PsqlDB struct {
 27	*postgres.PsqlDB
 28}
 29
 30var _ DB = (*PsqlDB)(nil)
 31
 32func NewDB(base *postgres.PsqlDB) *PsqlDB {
 33	return &PsqlDB{PsqlDB: base}
 34}
 35
 36func (me *PsqlDB) UpsertPipeMonitor(userID, topic string, dur time.Duration, winEnd *time.Time) error {
 37	durStr := fmt.Sprintf("%d seconds", int64(dur.Seconds()))
 38	_, err := me.Db.Exec(
 39		`INSERT INTO pipe_monitors (user_id, topic, window_dur, window_end)
 40		VALUES ($1, $2, $3::interval, $4)
 41		ON CONFLICT (user_id, topic) DO UPDATE SET window_dur = $3::interval, window_end = $4, updated_at = NOW();`,
 42		userID,
 43		topic,
 44		durStr,
 45		winEnd,
 46	)
 47	return err
 48}
 49
 50func (me *PsqlDB) UpdatePipeMonitorLastPing(userID, topic string, lastPing *time.Time) error {
 51	_, err := me.Db.Exec(
 52		`UPDATE pipe_monitors SET last_ping = $3, updated_at = NOW() WHERE user_id = $1 AND topic = $2;`,
 53		userID,
 54		topic,
 55		lastPing,
 56	)
 57	return err
 58}
 59
 60func (me *PsqlDB) RemovePipeMonitor(userID, topic string) error {
 61	_, err := me.Db.Exec(
 62		`DELETE FROM pipe_monitors WHERE user_id = $1 AND topic = $2;`,
 63		userID,
 64		topic,
 65	)
 66	return err
 67}
 68
 69func (me *PsqlDB) FindPipeMonitorByTopic(userID, topic string) (*db.PipeMonitor, error) {
 70	monitor := &db.PipeMonitor{}
 71	err := me.Db.Get(monitor, `SELECT id, user_id, topic, (EXTRACT(EPOCH FROM window_dur) * 1000000000)::bigint as window_dur, window_end, last_ping, created_at, updated_at FROM pipe_monitors WHERE user_id = $1 AND topic = $2;`, userID, topic)
 72	if err != nil {
 73		return nil, err
 74	}
 75	return monitor, nil
 76}
 77
 78func (me *PsqlDB) FindPipeMonitorsByUser(userID string) ([]*db.PipeMonitor, error) {
 79	var monitors []*db.PipeMonitor
 80	err := me.Db.Select(&monitors, `SELECT id, user_id, topic, (EXTRACT(EPOCH FROM window_dur) * 1000000000)::bigint as window_dur, window_end, last_ping, created_at, updated_at FROM pipe_monitors WHERE user_id = $1 ORDER BY topic;`, userID)
 81	if err != nil {
 82		return nil, err
 83	}
 84	return monitors, nil
 85}
 86
 87func (me *PsqlDB) InsertPipeMonitorHistory(monitorID string, windowDur time.Duration, windowEnd, lastPing *time.Time) error {
 88	durStr := fmt.Sprintf("%d seconds", int64(windowDur.Seconds()))
 89	_, err := me.Db.Exec(
 90		`INSERT INTO pipe_monitors_history (monitor_id, window_dur, window_end, last_ping) VALUES ($1, $2::interval, $3, $4)`,
 91		monitorID, durStr, windowEnd, lastPing,
 92	)
 93	return err
 94}
 95
 96func (me *PsqlDB) FindPipeMonitorHistory(monitorID string, from, to time.Time) ([]*db.PipeMonitorHistory, error) {
 97	var history []*db.PipeMonitorHistory
 98	err := me.Db.Select(
 99		&history,
100		`SELECT id, monitor_id, (EXTRACT(EPOCH FROM window_dur) * 1000000000)::bigint as window_dur, window_end, last_ping, created_at, updated_at FROM pipe_monitors_history WHERE monitor_id = $1 AND last_ping <= $2 AND window_end >= $3 ORDER BY last_ping ASC`,
101		monitorID, to, from,
102	)
103	if err != nil {
104		return nil, err
105	}
106	return history, nil
107}