# Directory: internal/db/db.go

# Directory: internal/db/repo.go

# Directory: internal/glue/conversations.go

# Directory: internal/glue/branches.go

# Directory: internal/glue/completion.go

# Directory: internal/glue/linearize.go

# Directory: internal/http/router.go

# Directory: internal/http/middleware.go
package http

import "net/http"

func withCommon(next http.Handler) http.Handler {
	return http.TimeoutHandler(next, 60_000_000_000, "timeout") // 60s
}

# Directory: internal/http/handlers.go
package http

import (
	"encoding/json"
	"net/http"
	"strconv"

	"mind/internal/glue"
)

// POST /conversations  {"title":"demo","owner_id":1}
// GET  /conversations?owner_id=1
func (s *server) conversations(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodPost:
		var in struct { Title string `json:"title"`; OwnerID int64 `json:"owner_id"` }
		if err := json.NewDecoder(r.Body).Decode(&in); err != nil { writeJSON(w, 400, map[string]string{"error":"bad json"}); return }
		id, err := s.glue.CreateConversation(r.Context(), in.OwnerID, in.Title)
		if err != nil { writeJSON(w, 500, map[string]string{"error": err.Error()}); return }
		writeJSON(w, 200, map[string]any{"id": id})
	case http.MethodGet:
		ownerStr := r.URL.Query().Get("owner_id")
		owner, _ := strconv.ParseInt(ownerStr, 10, 64)
		out, err := s.glue.ListConversations(r.Context(), owner)
		if err != nil { writeJSON(w, 500, map[string]string{"error": err.Error()}); return }
		writeJSON(w, 200, out)
	default:
		w.WriteHeader(405)
	}
}

// POST /branches {conversation_id, name, head_node_id}
func (s *server) branches(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost { w.WriteHeader(405); return }
	var in glue.ForkReq
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil { writeJSON(w, 400, map[string]string{"error":"bad json"}); return }
	b, err := s.glue.ForkBranch(r.Context(), in)
	if err != nil { writeJSON(w, 500, map[string]string{"error": err.Error()}); return }
	writeJSON(w, 200, b)
}

// POST /completion {conversation_id, branch, prompt}
func (s *server) completion(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost { w.WriteHeader(405); return }
	var in glue.CompletionReq
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil { writeJSON(w, 400, map[string]string{"error":"bad json"}); return }
	resp, err := s.glue.AppendCompletion(r.Context(), in)
	if err != nil { writeJSON(w, 500, map[string]string{"error": err.Error()}); return }
	writeJSON(w, 200, resp)
}

// GET /linearize?conversation_id=..&branch=main
func (s *server) linearize(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet { w.WriteHeader(405); return }
	convID, _ := strconv.ParseInt(r.URL.Query().Get("conversation_id"), 10, 64)
	branch := r.URL.Query().Get("branch")
	out, err := s.glue.LinearizeByBranch(r.Context(), convID, branch)
	if err != nil { writeJSON(w, 500, map[string]string{"error": err.Error()}); return }
	writeJSON(w, 200, out)
}

# Directory: migrations/0001_init.sql
-- users (minimal for v0)
CREATE TABLE IF NOT EXISTS users (
  id           INTEGER PRIMARY KEY AUTOINCREMENT,
  email        VARCHAR(255) UNIQUE,
  pass_bcrypt  BLOB,
  created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- conversations
CREATE TABLE IF NOT EXISTS conversations (
  id           INTEGER PRIMARY KEY AUTOINCREMENT,
  owner_id     INTEGER NOT NULL,
  title        VARCHAR(255),
  created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- nodes
CREATE TABLE IF NOT EXISTS nodes (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  conversation_id INTEGER NOT NULL,
  author_kind     ENUM('user','assistant') NOT NULL,
  content         TEXT NOT NULL,
  created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- edges (DAG)
CREATE TABLE IF NOT EXISTS edges (
  parent_id    INTEGER NOT NULL,
  child_id     INTEGER NOT NULL,
  PRIMARY KEY(parent_id, child_id)
);

-- branches (named pointers)
CREATE TABLE IF NOT EXISTS branches (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  conversation_id INTEGER NOT NULL,
  name            VARCHAR(128) NOT NULL,
  head_node_id    INTEGER NOT NULL,
  UNIQUE (conversation_id, name)
);
