Files
silo/internal/db/workstations.go
Forbes a851630d85 feat(sessions): workstation table, registration API, and module scaffold
- Add 022_workstations.sql migration (UUID PK, user_id FK, UNIQUE(user_id, name))
- Add Sessions module (depends on Auth, default enabled) with config toggle
- Add WorkstationRepository with Upsert, GetByID, ListByUser, Touch, Delete
- Add workstation handlers: register (POST upsert), list (GET), delete (DELETE)
- Add /api/workstations routes gated by sessions module
- Wire WorkstationRepository into Server struct
- Update module tests for new Sessions module

Closes #161
2026-03-01 09:56:43 -06:00

96 lines
2.6 KiB
Go

package db
import (
"context"
"time"
"github.com/jackc/pgx/v5"
)
// Workstation represents a registered client machine.
type Workstation struct {
ID string
Name string
UserID string
Hostname string
LastSeen time.Time
CreatedAt time.Time
}
// WorkstationRepository provides workstation database operations.
type WorkstationRepository struct {
db *DB
}
// NewWorkstationRepository creates a new workstation repository.
func NewWorkstationRepository(db *DB) *WorkstationRepository {
return &WorkstationRepository{db: db}
}
// Upsert registers a workstation, updating hostname and last_seen if it already exists.
func (r *WorkstationRepository) Upsert(ctx context.Context, w *Workstation) error {
return r.db.pool.QueryRow(ctx, `
INSERT INTO workstations (name, user_id, hostname)
VALUES ($1, $2, $3)
ON CONFLICT (user_id, name) DO UPDATE
SET hostname = EXCLUDED.hostname, last_seen = now()
RETURNING id, last_seen, created_at
`, w.Name, w.UserID, w.Hostname).Scan(&w.ID, &w.LastSeen, &w.CreatedAt)
}
// GetByID returns a workstation by its ID.
func (r *WorkstationRepository) GetByID(ctx context.Context, id string) (*Workstation, error) {
w := &Workstation{}
err := r.db.pool.QueryRow(ctx, `
SELECT id, name, user_id, hostname, last_seen, created_at
FROM workstations
WHERE id = $1
`, id).Scan(&w.ID, &w.Name, &w.UserID, &w.Hostname, &w.LastSeen, &w.CreatedAt)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return w, nil
}
// ListByUser returns all workstations for a user.
func (r *WorkstationRepository) ListByUser(ctx context.Context, userID string) ([]*Workstation, error) {
rows, err := r.db.pool.Query(ctx, `
SELECT id, name, user_id, hostname, last_seen, created_at
FROM workstations
WHERE user_id = $1
ORDER BY name
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var workstations []*Workstation
for rows.Next() {
w := &Workstation{}
if err := rows.Scan(&w.ID, &w.Name, &w.UserID, &w.Hostname, &w.LastSeen, &w.CreatedAt); err != nil {
return nil, err
}
workstations = append(workstations, w)
}
return workstations, rows.Err()
}
// Touch updates a workstation's last_seen timestamp.
func (r *WorkstationRepository) Touch(ctx context.Context, id string) error {
_, err := r.db.pool.Exec(ctx, `
UPDATE workstations SET last_seen = now() WHERE id = $1
`, id)
return err
}
// Delete removes a workstation.
func (r *WorkstationRepository) Delete(ctx context.Context, id string) error {
_, err := r.db.pool.Exec(ctx, `DELETE FROM workstations WHERE id = $1`, id)
return err
}