feat: init sproutclaw-web — Go+Gin backend + React frontend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
191
backend/internal/handlers/sessions.go
Normal file
191
backend/internal/handlers/sessions.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"sproutclaw-web/internal/config"
|
||||
"sproutclaw-web/internal/db"
|
||||
"sproutclaw-web/internal/models"
|
||||
"sproutclaw-web/internal/rpc"
|
||||
"sproutclaw-web/internal/services"
|
||||
"sproutclaw-web/internal/utils"
|
||||
)
|
||||
|
||||
// RegisterSessions registers all session management routes.
|
||||
func RegisterSessions(r *gin.RouterGroup, cfg *config.Config, database *db.DB, piClient *rpc.Client) {
|
||||
r.GET("/sessions", handleListSessions(cfg, database))
|
||||
r.POST("/new-session", handleNewSession(piClient))
|
||||
r.POST("/sessions/history", handleSessionHistory(cfg))
|
||||
r.POST("/sessions/delete", handleDeleteSession(cfg, database))
|
||||
r.POST("/sessions/pin", handlePinSession(cfg, database))
|
||||
r.POST("/sessions/load", handleLoadSession(cfg, piClient))
|
||||
r.POST("/sessions/activate", handleActivateSession(cfg, piClient))
|
||||
r.POST("/sessions/name", handleNameSession(cfg))
|
||||
r.GET("/session-state", handleSessionState(piClient))
|
||||
}
|
||||
|
||||
func handleListSessions(cfg *config.Config, database *db.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
summaries, err := services.BuildSessionList(cfg.SessionsDir, database)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if summaries == nil {
|
||||
summaries = []models.SessionSummary{}
|
||||
}
|
||||
c.JSON(http.StatusOK, models.SessionsResponse{Sessions: summaries})
|
||||
}
|
||||
}
|
||||
|
||||
func handleNewSession(piClient *rpc.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
resp, err := piClient.SendCmd(models.RPCCommand{Type: "new_session"})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, jsonRaw(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func handleSessionHistory(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.HistoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
lines, err := services.ReadSessionMessages(req.Path)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"messages": lines})
|
||||
}
|
||||
}
|
||||
|
||||
func handleDeleteSession(cfg *config.Config, database *db.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.DeleteSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
if err := os.Remove(req.Path); err != nil && !os.IsNotExist(err) {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
_ = database.SetSessionPinned(req.Path, false)
|
||||
c.JSON(http.StatusOK, models.OKResponse{OK: true})
|
||||
}
|
||||
}
|
||||
|
||||
func handlePinSession(cfg *config.Config, database *db.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.PinSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
if err := database.SetSessionPinned(req.Path, req.Pinned); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, models.OKResponse{OK: true})
|
||||
}
|
||||
}
|
||||
|
||||
func handleLoadSession(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.LoadSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
lines, err := services.ReadSessionMessages(req.Path)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
resp, err := piClient.SendCmd(models.RPCCommand{
|
||||
Type: "switch_session",
|
||||
Path: req.Path,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"messages": lines, "result": jsonRaw(resp.Result)})
|
||||
}
|
||||
}
|
||||
|
||||
func handleActivateSession(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.ActivateSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
resp, err := piClient.SendCmd(models.RPCCommand{
|
||||
Type: "switch_session",
|
||||
Path: req.Path,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, jsonRaw(resp.Result))
|
||||
}
|
||||
}
|
||||
|
||||
func handleNameSession(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req models.NameSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
|
||||
c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
|
||||
return
|
||||
}
|
||||
if err := services.AppendSessionName(req.Path, req.Name); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, models.OKResponse{OK: true})
|
||||
}
|
||||
}
|
||||
|
||||
func handleSessionState(piClient *rpc.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
snap := piClient.GetSnapshot()
|
||||
c.JSON(http.StatusOK, models.SessionStateResponse{
|
||||
IsStreaming: snap.IsStreaming,
|
||||
SessionFile: snap.SessionFile,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user