// Prompt sync server. No authentication: a sync group is identified by an // 8-digit code (1234-5678) that acts as the shared secret. Whoever has the // code can read and overwrite the group's data. // // Endpoints: // // POST /api/codes -> {"code":"1234-5678"} (create group) // GET /api/sync/{code} -> {"version":N,"data":{...}} (404 if unknown) // PUT /api/sync/{code} -> {"version":N+1} (upsert payload) // GET /healthz -> 200 // // Config: // // PROMPT_SYNC_DATABASE_URL (default postgres://localhost:5432/prompt_sync?sslmode=disable) // PROMPT_SYNC_ADDR (default :8080) package main import ( "crypto/rand" "database/sql" "encoding/json" "fmt" "log" "net/http" "os" "regexp" "time" _ "github.com/lib/pq" ) const schema = ` CREATE TABLE IF NOT EXISTS sync_groups ( code TEXT PRIMARY KEY, version BIGINT NOT NULL DEFAULT 0, data JSONB, updated_at TIMESTAMPTZ NOT NULL DEFAULT now() )` var codePattern = regexp.MustCompile(`^\d{4}-\d{4}$`) type Server struct { db *sql.DB } func main() { dsn := os.Getenv("PROMPT_SYNC_DATABASE_URL") if dsn == "" { dsn = "postgres://localhost:5432/prompt_sync?sslmode=disable" } addr := os.Getenv("PROMPT_SYNC_ADDR") if addr == "" { addr = ":8080" } db, err := sql.Open("postgres", dsn) if err != nil { log.Fatalf("open database: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("connect to database: %v", err) } if _, err := db.Exec(schema); err != nil { log.Fatalf("ensure schema: %v", err) } s := &Server{db: db} mux := http.NewServeMux() mux.HandleFunc("POST /api/codes", s.createCode) mux.HandleFunc("GET /api/sync/{code}", s.getSync) mux.HandleFunc("PUT /api/sync/{code}", s.putSync) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) log.Printf("prompt-sync listening on %s", addr) log.Fatal(http.ListenAndServe(addr, mux)) } // createCode generates a unique 8-digit code, formats it as 1234-5678, and // inserts an empty group so the code exists before the first PUT. func (s *Server) createCode(w http.ResponseWriter, _ *http.Request) { for range 10 { code, err := newCode() if err != nil { writeError(w, http.StatusInternalServerError, "could not generate code") return } var exists bool err = s.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM sync_groups WHERE code = $1)`, code).Scan(&exists) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } if exists { continue } if _, err := s.db.Exec(`INSERT INTO sync_groups (code) VALUES ($1)`, code); err != nil { writeError(w, http.StatusInternalServerError, "database error") return } writeJSON(w, http.StatusOK, map[string]string{"code": code}) return } writeError(w, http.StatusInternalServerError, "could not find a free code") } func (s *Server) getSync(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") if !codePattern.MatchString(code) { writeError(w, http.StatusBadRequest, "code must look like 1234-5678") return } var ( version int64 data []byte updatedAt time.Time ) err := s.db.QueryRow( `SELECT version, data, updated_at FROM sync_groups WHERE code = $1`, code, ).Scan(&version, &data, &updatedAt) if err == sql.ErrNoRows { writeError(w, http.StatusNotFound, "unknown code") return } if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } if data == nil { data = []byte("null") } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"version":%d,"updatedAt":%q,"data":%s}`, version, updatedAt.UTC().Format(time.RFC3339), data) } func (s *Server) putSync(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") if !codePattern.MatchString(code) { writeError(w, http.StatusBadRequest, "code must look like 1234-5678") return } body := http.MaxBytesReader(w, r.Body, 50<<20) // 50 MB ceiling (images inside) defer body.Close() var payload json.RawMessage if err := json.NewDecoder(body).Decode(&payload); err != nil { writeError(w, http.StatusBadRequest, "body must be JSON") return } var version int64 err := s.db.QueryRow(` INSERT INTO sync_groups (code, version, data, updated_at) VALUES ($1, 1, $2, now()) ON CONFLICT (code) DO UPDATE SET version = sync_groups.version + 1, data = $2, updated_at = now() RETURNING version`, code, payload).Scan(&version) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } writeJSON(w, http.StatusOK, map[string]int64{"version": version}) } func newCode() (string, error) { var b [8]byte if _, err := rand.Read(b[:]); err != nil { return "", err } digits := make([]byte, 8) for i, v := range b { digits[i] = '0' + v%10 } return fmt.Sprintf("%s-%s", digits[:4], digits[4:]), nil } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]string{"error": message}) }