- 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
96 lines
2.6 KiB
Go
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
|
|
}
|