serve

package
v0.43.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 24, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package serve provides port file lifecycle management and web session bootstrap for the td serve HTTP server.

Package serve provides the HTTP API layer for td serve, including response envelopes, DTOs with explicit JSON serialization, and request validation helpers.

Index

Constants

View Source
const (
	ErrValidation   = "validation_error" // 400
	ErrNotFound     = "not_found"        // 404
	ErrConflict     = "conflict"         // 409
	ErrUnauthorized = "unauthorized"     // 401
	ErrForbidden    = "forbidden"        // 403
	ErrInternal     = "internal"         // 500
)

Standard error codes mapped to HTTP status codes.

Variables

This section is empty.

Functions

func BumpSessionActivity

func BumpSessionActivity(database *db.DB, sessionID string) error

BumpSessionActivity updates the last_activity timestamp for a session.

func DeletePortFile

func DeletePortFile(baseDir string) error

DeletePortFile removes the port file. Called on server shutdown. Errors are returned but callers may choose to ignore them during cleanup.

func GenerateInstanceID

func GenerateInstanceID() (string, error)

GenerateInstanceID creates a new random instance ID with the srv_ prefix and 6 random hex characters (e.g. "srv_8f3b2c").

func GetOrCreateWebSession

func GetOrCreateWebSession(database *db.DB) (*db.SessionRow, error)

GetOrCreateWebSession finds or creates the shared web session used by the td serve HTTP server. The session is identified by:

  • agent_type = "web"
  • agent_pid = 0
  • branch = "default"

If a matching session exists, its activity timestamp is bumped. If none exists, a new session named "td-serve-web" is created.

func IsPortFileStale

func IsPortFileStale(info *PortInfo) bool

IsPortFileStale checks whether the port file describes a server that is no longer running. A port file is stale if:

  • The PID is not alive, OR
  • The PID is alive but the health endpoint doesn't respond

func IsServerHealthy

func IsServerHealthy(port int) bool

IsServerHealthy checks if a server at the given port is alive by sending an HTTP GET to localhost:{port}/health. Returns true only if a 200 response is received within the health timeout.

func StartSessionHeartbeat

func StartSessionHeartbeat(ctx context.Context, database *db.DB, sessionID string)

StartSessionHeartbeat launches a goroutine that periodically bumps the session's last_activity timestamp. The goroutine stops when the provided context is cancelled. Errors during bumps are silently ignored since heartbeats are best-effort.

func WriteError

func WriteError(w http.ResponseWriter, code, message string, status int)

WriteError writes a JSON error envelope.

func WritePortFile

func WritePortFile(baseDir string, info *PortInfo) error

WritePortFile writes the port file to baseDir/.todos/serve-port after acquiring an exclusive lock on the lock file. The lock is released after writing. This ensures only one server instance can register itself at a time.

func WriteSuccess

func WriteSuccess(w http.ResponseWriter, data interface{}, status int)

WriteSuccess writes a JSON success envelope with the given data and status.

func WriteValidation

func WriteValidation(w http.ResponseWriter, fields []FieldError)

WriteValidation writes a 400 validation_error response with field-level details.

Types

type ActivityItemDTO

type ActivityItemDTO struct {
	Timestamp    string `json:"timestamp"`
	SessionID    string `json:"session_id"`
	Type         string `json:"type"`
	IssueID      string `json:"issue_id"`
	IssueTitle   string `json:"issue_title"`
	Message      string `json:"message"`
	LogType      string `json:"log_type"`
	Action       string `json:"action"`
	EntityID     string `json:"entity_id"`
	EntityType   string `json:"entity_type"`
	PreviousData string `json:"previous_data"`
	NewData      string `json:"new_data"`
}

ActivityItemDTO is the API representation of a unified activity feed item.

func ActivityItemToDTO

func ActivityItemToDTO(item *monitor.ActivityItem) ActivityItemDTO

ActivityItemToDTO converts a monitor.ActivityItem to an ActivityItemDTO.

func ActivityItemsToDTOs

func ActivityItemsToDTOs(items []monitor.ActivityItem) []ActivityItemDTO

ActivityItemsToDTOs converts a slice of activity items to DTOs.

type BoardCreateBody

type BoardCreateBody struct {
	Name  string `json:"name"`
	Query string `json:"query"`
}

BoardCreateBody represents the expected JSON body for creating a board.

type BoardDTO

type BoardDTO struct {
	ID           string  `json:"id"`
	Name         string  `json:"name"`
	Query        string  `json:"query"`
	IsBuiltin    bool    `json:"is_builtin"`
	ViewMode     string  `json:"view_mode"`
	LastViewedAt *string `json:"last_viewed_at"`
	CreatedAt    string  `json:"created_at"`
	UpdatedAt    string  `json:"updated_at"`
}

BoardDTO is the API representation of a board.

func BoardToDTO

func BoardToDTO(board *models.Board) BoardDTO

BoardToDTO converts a models.Board to a BoardDTO.

func BoardsToDTOs

func BoardsToDTOs(boards []models.Board) []BoardDTO

BoardsToDTOs converts a slice of boards to DTOs.

type BoardPositionBody

type BoardPositionBody struct {
	IssueID  string `json:"issue_id"`
	Position int    `json:"position"`
}

BoardPositionBody represents the expected JSON body for setting an issue position on a board.

type BoardUpdateBody

type BoardUpdateBody struct {
	Name  *string `json:"name"`
	Query *string `json:"query"`
}

BoardUpdateBody represents the expected JSON body for updating a board. All fields are optional; only present fields are applied.

type CommentCreateBody

type CommentCreateBody struct {
	Text string `json:"text"`
}

CommentCreateBody represents the expected JSON body for adding a comment.

type CommentDTO

type CommentDTO struct {
	ID        string `json:"id"`
	IssueID   string `json:"issue_id"`
	SessionID string `json:"session_id"`
	Text      string `json:"text"`
	CreatedAt string `json:"created_at"`
}

CommentDTO is the API representation of a comment.

func CommentToDTO

func CommentToDTO(comment *models.Comment) CommentDTO

CommentToDTO converts a models.Comment to a CommentDTO.

func CommentsToDTOs

func CommentsToDTOs(comments []models.Comment) []CommentDTO

CommentsToDTOs converts a slice of comments to DTOs.

type DependencyCreateBody

type DependencyCreateBody struct {
	DependsOn string `json:"depends_on"`
}

DependencyCreateBody represents the expected JSON body for adding a dependency.

type DependencyDTO

type DependencyDTO struct {
	DepID        string `json:"dep_id"`
	IssueID      string `json:"issue_id"`
	DependsOnID  string `json:"depends_on_id"`
	RelationType string `json:"relation_type"`
}

DependencyDTO is the API representation of an issue dependency.

func DependenciesToDTOs

func DependenciesToDTOs(deps []models.IssueDependency) []DependencyDTO

DependenciesToDTOs converts a slice of dependencies to DTOs.

func DependencyToDTO

func DependencyToDTO(dep *models.IssueDependency) DependencyDTO

DependencyToDTO converts a models.IssueDependency to a DependencyDTO.

type Envelope

type Envelope struct {
	OK    bool          `json:"ok"`
	Data  interface{}   `json:"data,omitempty"`
	Error *ErrorPayload `json:"error,omitempty"`
}

Envelope is the standard response wrapper for all API responses. Success: {"ok": true, "data": {...}} Error: {"ok": false, "error": {"code": "...", "message": "...", "details": ...}}

type ErrorPayload

type ErrorPayload struct {
	Code    string      `json:"code"`
	Message string      `json:"message"`
	Details interface{} `json:"details,omitempty"`
}

ErrorPayload holds structured error information.

type FieldError

type FieldError struct {
	Field    string      `json:"field"`
	Rule     string      `json:"rule"`
	Value    interface{} `json:"value,omitempty"`
	Expected interface{} `json:"expected,omitempty"`
	Message  string      `json:"message"`
}

FieldError describes a single validation failure on a request field.

func ValidateIssueCreate

func ValidateIssueCreate(body *IssueCreateBody, titleMin, titleMax int) []FieldError

ValidateIssueCreate validates an IssueCreateBody and returns any field errors. The titleMin and titleMax parameters allow callers to pass configured limits.

func ValidateIssueUpdate

func ValidateIssueUpdate(body *IssueUpdateBody, titleMin, titleMax int) []FieldError

ValidateIssueUpdate validates an IssueUpdateBody and returns any field errors. The titleMin and titleMax parameters allow callers to pass configured limits.

func ValidatePagination

func ValidatePagination(limit, offset int) []FieldError

ValidatePagination validates limit and offset query parameters.

type FocusBody

type FocusBody struct {
	IssueID *string `json:"issue_id"`
}

FocusBody represents the expected JSON body for setting focus. IssueID is a pointer so that null/absent can be distinguished from empty string.

type HandoffDTO

type HandoffDTO struct {
	ID        string   `json:"id"`
	IssueID   string   `json:"issue_id"`
	SessionID string   `json:"session_id"`
	Done      []string `json:"done"`
	Remaining []string `json:"remaining"`
	Decisions []string `json:"decisions"`
	Uncertain []string `json:"uncertain"`
	Timestamp string   `json:"timestamp"`
}

HandoffDTO is the API representation of a handoff.

func HandoffToDTO

func HandoffToDTO(handoff *models.Handoff) HandoffDTO

HandoffToDTO converts a models.Handoff to a HandoffDTO.

type IssueCreateBody

type IssueCreateBody struct {
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Type        string   `json:"type"`
	Priority    string   `json:"priority"`
	Points      int      `json:"points"`
	Labels      []string `json:"labels"`
	ParentID    string   `json:"parent_id"`
	Acceptance  string   `json:"acceptance"`
	Sprint      string   `json:"sprint"`
	Minor       bool     `json:"minor"`
	DeferUntil  string   `json:"defer_until"`
	DueDate     string   `json:"due_date"`
}

IssueCreateBody represents the expected JSON body for creating an issue.

type IssueDTO

type IssueDTO struct {
	ID                 string   `json:"id"`
	Title              string   `json:"title"`
	Description        string   `json:"description"`
	Status             string   `json:"status"`
	Type               string   `json:"type"`
	Priority           string   `json:"priority"`
	Points             int      `json:"points"`
	Labels             []string `json:"labels"`
	ParentID           *string  `json:"parent_id"`
	Acceptance         string   `json:"acceptance"`
	Sprint             string   `json:"sprint"`
	ImplementerSession *string  `json:"implementer_session"`
	CreatorSession     *string  `json:"creator_session"`
	ReviewerSession    *string  `json:"reviewer_session"`
	CreatedAt          string   `json:"created_at"`
	UpdatedAt          string   `json:"updated_at"`
	ClosedAt           *string  `json:"closed_at"`
	DeletedAt          *string  `json:"deleted_at"`
	Minor              bool     `json:"minor"`
	CreatedBranch      *string  `json:"created_branch"`
	DeferUntil         *string  `json:"defer_until"`
	DueDate            *string  `json:"due_date"`
	DeferCount         int      `json:"defer_count"`
}

IssueDTO is the API representation of an issue. All documented fields are always present (no omitempty for documented fields). Nullable fields use *string so they serialize as JSON null when nil. Collections serialize as [] when empty, never null.

func IssueToDTO

func IssueToDTO(issue *models.Issue) IssueDTO

IssueToDTO converts a models.Issue to an IssueDTO with proper null/empty handling for the API layer.

func IssuesToDTOs

func IssuesToDTOs(issues []models.Issue) []IssueDTO

IssuesToDTOs converts a slice of issues to DTOs.

type IssueUpdateBody

type IssueUpdateBody struct {
	Title       *string  `json:"title"`
	Description *string  `json:"description"`
	Type        *string  `json:"type"`
	Priority    *string  `json:"priority"`
	Points      *int     `json:"points"`
	Labels      []string `json:"labels"`
	ParentID    *string  `json:"parent_id"`
	Acceptance  *string  `json:"acceptance"`
	Sprint      *string  `json:"sprint"`
	Minor       *bool    `json:"minor"`
	DeferUntil  *string  `json:"defer_until"`
	DueDate     *string  `json:"due_date"`
}

IssueUpdateBody represents the expected JSON body for updating an issue. All fields are optional; only present fields are applied.

type LogDTO

type LogDTO struct {
	ID            string `json:"id"`
	IssueID       string `json:"issue_id"`
	SessionID     string `json:"session_id"`
	WorkSessionID string `json:"work_session_id"`
	Message       string `json:"message"`
	Type          string `json:"type"`
	Timestamp     string `json:"timestamp"`
}

LogDTO is the API representation of a session log entry.

func LogToDTO

func LogToDTO(log *models.Log) LogDTO

LogToDTO converts a models.Log to a LogDTO.

func LogsToDTOs

func LogsToDTOs(logs []models.Log) []LogDTO

LogsToDTOs converts a slice of logs to DTOs.

type MonitorDTO

type MonitorDTO struct {
	FocusedIssue   *IssueDTO          `json:"focused_issue"`
	InProgress     []IssueDTO         `json:"in_progress"`
	Activity       []ActivityItemDTO  `json:"activity"`
	TaskList       TaskListDTO        `json:"task_list"`
	RecentHandoffs []RecentHandoffDTO `json:"recent_handoffs"`
	ActiveSessions []string           `json:"active_sessions"`
	Timestamp      string             `json:"timestamp"`
}

MonitorDTO is the API representation of the full monitor state.

func MonitorDataToDTO

func MonitorDataToDTO(msg *monitor.RefreshDataMsg) MonitorDTO

MonitorDataToDTO converts a RefreshDataMsg to a MonitorDTO.

type PaginatedResponse

type PaginatedResponse struct {
	Items      interface{}   `json:"items"`
	Pagination PaginationDTO `json:"pagination"`
}

PaginatedResponse wraps a list result with pagination metadata.

type PaginationDTO

type PaginationDTO struct {
	Limit  int `json:"limit"`
	Offset int `json:"offset"`
	Total  int `json:"total"`
}

PaginationDTO describes the pagination state returned alongside list results.

type PortInfo

type PortInfo struct {
	Port       int       `json:"port"`
	PID        int       `json:"pid"`
	StartedAt  time.Time `json:"started_at"`
	InstanceID string    `json:"instance_id"`
}

PortInfo contains the metadata written to the port file when the server starts.

func ReadPortFile

func ReadPortFile(baseDir string) (*PortInfo, error)

ReadPortFile reads and parses the port file from baseDir/.todos/serve-port. Returns an error if the file doesn't exist or is missing required fields.

type RecentHandoffDTO

type RecentHandoffDTO struct {
	IssueID   string `json:"issue_id"`
	SessionID string `json:"session_id"`
	Timestamp string `json:"timestamp"`
}

RecentHandoffDTO is the API representation of a recent handoff summary.

type SSEEvent

type SSEEvent struct {
	ID    string // change_token used as event ID
	Event string // "refresh" or "ping"
	Data  string // JSON payload
}

SSEEvent represents a single Server-Sent Event.

type SSEHub

type SSEHub struct {
	// contains filtered or unexported fields
}

SSEHub manages connected SSE clients and broadcasts events.

func NewSSEHub

func NewSSEHub(database *db.DB, pollInterval time.Duration) *SSEHub

NewSSEHub creates a new SSEHub with the given database and poll interval.

func (*SSEHub) Broadcast

func (h *SSEHub) Broadcast(changeToken string)

Broadcast sends a refresh event to all connected clients with the given change token.

func (*SSEHub) Start

func (h *SSEHub) Start(ctx context.Context)

Start begins the background polling goroutine that checks for change_token updates and sends periodic pings.

func (*SSEHub) Stop

func (h *SSEHub) Stop()

Stop shuts down the SSE hub, closing all client channels and stopping the polling goroutine.

type ServeConfig

type ServeConfig struct {
	Port         int
	Addr         string
	Token        string
	CORSOrigin   string
	PollInterval time.Duration
}

ServeConfig holds the configuration for the HTTP server.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the td serve HTTP server.

func NewServer

func NewServer(database *db.DB, baseDir, sessionID string, config ServeConfig) *Server

NewServer creates a new Server, registers all routes, and sets up the middleware chain. Handlers are placeholder 501s until subsequent tasks implement them.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the mux wrapped in the middleware chain.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(ctx context.Context) error

ListenAndServe starts the HTTP server on the configured address and port, and handles graceful shutdown when the context is cancelled.

func (*Server) NotifyChange

func (s *Server) NotifyChange()

NotifyChange is called after successful write operations. It: 1. Gets the current change_token 2. Broadcasts a refresh event to all SSE clients 3. Triggers a debounced autosync

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully stops the HTTP server. If the server has not been started, this is a no-op.

func (*Server) StartBackground

func (s *Server) StartBackground(ctx context.Context)

StartBackground starts long-lived background processes (SSE polling loop).

func (*Server) StopBackground

func (s *Server) StopBackground()

StopBackground stops long-lived background processes.

type SessionDTO

type SessionDTO struct {
	ID                string  `json:"id"`
	Name              string  `json:"name"`
	Branch            string  `json:"branch"`
	AgentType         string  `json:"agent_type"`
	AgentPID          int     `json:"agent_pid"`
	ContextID         string  `json:"context_id"`
	PreviousSessionID *string `json:"previous_session_id"`
	StartedAt         string  `json:"started_at"`
	LastActivity      string  `json:"last_activity"`
}

SessionDTO is the API representation of a session.

func SessionToDTO

func SessionToDTO(sess *session.Session) SessionDTO

SessionToDTO converts a session.Session to a SessionDTO.

func SessionsToDTOs

func SessionsToDTOs(sessions []session.Session) []SessionDTO

SessionsToDTOs converts a slice of sessions to DTOs.

type StatsDTO

type StatsDTO struct {
	Total      int            `json:"total"`
	ByStatus   map[string]int `json:"by_status"`
	ByType     map[string]int `json:"by_type"`
	ByPriority map[string]int `json:"by_priority"`

	OldestOpen      *IssueDTO `json:"oldest_open"`
	NewestTask      *IssueDTO `json:"newest_task"`
	LastClosed      *IssueDTO `json:"last_closed"`
	CreatedToday    int       `json:"created_today"`
	CreatedThisWeek int       `json:"created_this_week"`

	TotalPoints      int     `json:"total_points"`
	AvgPointsPerTask float64 `json:"avg_points_per_task"`
	CompletionRate   float64 `json:"completion_rate"`

	TotalLogs         int    `json:"total_logs"`
	TotalHandoffs     int    `json:"total_handoffs"`
	MostActiveSession string `json:"most_active_session"`
}

StatsDTO is the API representation of extended project statistics.

func StatsToDTO

func StatsToDTO(stats *models.ExtendedStats) StatsDTO

StatsToDTO converts a models.ExtendedStats to a StatsDTO.

type TaskListDTO

type TaskListDTO struct {
	Reviewable    []IssueDTO `json:"reviewable"`
	NeedsRework   []IssueDTO `json:"needs_rework"`
	InProgress    []IssueDTO `json:"in_progress"`
	Ready         []IssueDTO `json:"ready"`
	PendingReview []IssueDTO `json:"pending_review"`
	Blocked       []IssueDTO `json:"blocked"`
	Closed        []IssueDTO `json:"closed"`
}

TaskListDTO is the API representation of categorized task lists.

type ValidationDetails

type ValidationDetails struct {
	Fields []FieldError `json:"fields"`
}

ValidationDetails wraps field-level validation errors in error.details.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL