WitNote / internal /profiles /handlers.go
AUXteam's picture
Upload folder using huggingface_hub
6a7089a verified
package profiles
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/pinchtab/pinchtab/internal/web"
)
func (pm *ProfileManager) RegisterHandlers(mux *http.ServeMux) {
mux.HandleFunc("GET /profiles", pm.handleList)
mux.HandleFunc("POST /profiles", pm.handleCreate)
mux.HandleFunc("POST /profiles/create", pm.handleCreate)
mux.HandleFunc("GET /profiles/{id}", pm.handleGetByID)
mux.HandleFunc("POST /profiles/import", pm.handleImport)
mux.HandleFunc("PATCH /profiles/meta", pm.handleUpdateMeta)
mux.HandleFunc("POST /profiles/{id}/reset", pm.handleResetByIDOrName)
mux.HandleFunc("GET /profiles/{id}/logs", pm.handleLogsByIDOrName)
mux.HandleFunc("GET /profiles/{id}/analytics", pm.handleAnalyticsByIDOrName)
mux.HandleFunc("DELETE /profiles/{id}", pm.handleDeleteByID)
mux.HandleFunc("PATCH /profiles/{id}", pm.handleUpdateByID)
}
func (pm *ProfileManager) handleList(w http.ResponseWriter, r *http.Request) {
profiles, err := pm.List()
if err != nil {
web.Error(w, 500, err)
return
}
showAll := r.URL.Query().Get("all") == "true"
if !showAll {
filtered := []map[string]any{}
for _, p := range profiles {
if !p.Temporary {
sizeMB := float64(p.DiskUsage) / (1024 * 1024)
filtered = append(filtered, map[string]any{
"id": p.ID,
"name": p.Name,
"path": p.Path,
"pathExists": p.PathExists,
"created": p.Created,
"lastUsed": p.LastUsed,
"diskUsage": p.DiskUsage,
"sizeMB": sizeMB,
"running": p.Running,
"source": p.Source,
"chromeProfileName": p.ChromeProfileName,
"accountEmail": p.AccountEmail,
"accountName": p.AccountName,
"hasAccount": p.HasAccount,
"useWhen": p.UseWhen,
"description": p.Description,
})
}
}
web.JSON(w, 200, filtered)
return
}
web.JSON(w, 200, profiles)
}
func (pm *ProfileManager) handleCreate(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Description string `json:"description"`
UseWhen string `json:"useWhen"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
web.Error(w, 400, err)
return
}
if req.Name == "" {
web.Error(w, 400, fmt.Errorf("name required"))
return
}
meta := ProfileMeta{
Description: req.Description,
UseWhen: req.UseWhen,
}
if err := pm.CreateWithMeta(req.Name, meta); err != nil {
// Validation errors → 400, already exists → 409, others → 500
if strings.Contains(err.Error(), "cannot contain") || strings.Contains(err.Error(), "cannot be empty") {
web.Error(w, 400, err)
} else if strings.Contains(err.Error(), "already exists") {
web.Error(w, 409, err)
} else {
web.Error(w, 500, err)
}
return
}
generatedID := profileID(req.Name)
web.JSON(w, 200, map[string]any{
"status": "created",
"id": generatedID,
"name": req.Name,
})
}
func (pm *ProfileManager) handleImport(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
SourcePath string `json:"sourcePath"`
Description string `json:"description"`
UseWhen string `json:"useWhen"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
web.Error(w, 400, err)
return
}
if req.Name == "" || req.SourcePath == "" {
web.Error(w, 400, fmt.Errorf("name and sourcePath required"))
return
}
meta := ProfileMeta{
Description: req.Description,
UseWhen: req.UseWhen,
}
if err := pm.ImportWithMeta(req.Name, req.SourcePath, meta); err != nil {
web.Error(w, 500, err)
return
}
web.JSON(w, 200, map[string]string{"status": "imported", "name": req.Name})
}
func (pm *ProfileManager) handleUpdateMeta(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Description *string `json:"description"`
UseWhen *string `json:"useWhen"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
web.Error(w, 400, err)
return
}
if req.Name == "" {
web.Error(w, 400, fmt.Errorf("name required"))
return
}
updates := make(map[string]string)
if req.Description != nil {
updates["description"] = *req.Description
}
if req.UseWhen != nil {
updates["useWhen"] = *req.UseWhen
}
if err := pm.UpdateMeta(req.Name, updates); err != nil {
web.Error(w, 500, err)
return
}
web.JSON(w, 200, map[string]string{"status": "updated", "name": req.Name})
}
func (pm *ProfileManager) handleGetByID(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
profiles, err := pm.List()
if err != nil {
web.Error(w, 500, err)
return
}
var foundProfile map[string]any
for _, p := range profiles {
if p.ID != id && p.Name != id {
continue
}
foundProfile = map[string]any{
"id": p.ID,
"name": p.Name,
"path": p.Path,
"pathExists": p.PathExists,
"created": p.Created,
"diskUsage": p.DiskUsage,
"sizeMB": float64(p.DiskUsage) / (1024 * 1024),
"source": p.Source,
"chromeProfileName": p.ChromeProfileName,
"accountEmail": p.AccountEmail,
"accountName": p.AccountName,
"hasAccount": p.HasAccount,
"useWhen": p.UseWhen,
"description": p.Description,
}
break
}
if foundProfile == nil {
web.Error(w, 404, fmt.Errorf("profile %q not found", id))
return
}
web.JSON(w, 200, foundProfile)
}
func (pm *ProfileManager) handleDeleteByID(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
name, err := pm.resolveIDOnly(id)
if err != nil {
web.Error(w, 404, err)
return
}
if err := pm.Delete(name); err != nil {
web.Error(w, 500, err)
return
}
web.JSON(w, 200, map[string]any{"status": "deleted", "id": id, "name": name})
}
func (pm *ProfileManager) resolveIDOrName(idOrName string) (string, error) {
name, err := pm.FindByID(idOrName)
if err == nil {
return name, nil
}
if pm.Exists(idOrName) {
return idOrName, nil
}
return "", fmt.Errorf("profile %q not found (not a valid ID or name)", idOrName)
}
func (pm *ProfileManager) resolveIDOnly(id string) (string, error) {
name, err := pm.FindByID(id)
if err != nil {
return "", fmt.Errorf("profile %q not found (must use profile ID, not name)", id)
}
return name, nil
}
func (pm *ProfileManager) handleUpdateByID(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
name, err := pm.resolveIDOnly(id)
if err != nil {
web.Error(w, 404, err)
return
}
var req struct {
Name *string `json:"name"`
UseWhen *string `json:"useWhen"`
Description *string `json:"description"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
web.Error(w, 400, fmt.Errorf("invalid JSON"))
return
}
finalName := name
if req.Name != nil && *req.Name != name {
if err := pm.Rename(name, *req.Name); err != nil {
if strings.Contains(err.Error(), "already exists") {
web.Error(w, 409, err)
} else if strings.Contains(err.Error(), "cannot contain") || strings.Contains(err.Error(), "cannot be empty") {
web.Error(w, 400, err)
} else {
web.Error(w, 500, err)
}
return
}
finalName = *req.Name
}
updates := make(map[string]string)
if req.Description != nil {
updates["description"] = *req.Description
}
if req.UseWhen != nil {
updates["useWhen"] = *req.UseWhen
}
if len(updates) > 0 {
if err := pm.UpdateMeta(finalName, updates); err != nil {
web.Error(w, 500, err)
return
}
}
web.JSON(w, 200, map[string]any{"status": "updated", "id": profileID(finalName), "name": finalName})
}
func (pm *ProfileManager) handleResetByIDOrName(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
name, err := pm.resolveIDOnly(id)
if err != nil {
web.Error(w, 404, err)
return
}
if err := pm.Reset(name); err != nil {
web.Error(w, 500, err)
return
}
web.JSON(w, 200, map[string]any{"status": "reset", "id": id, "name": name})
}
func (pm *ProfileManager) handleLogsByIDOrName(w http.ResponseWriter, r *http.Request) {
idOrName := r.PathValue("id")
name, err := pm.resolveIDOrName(idOrName)
if err != nil {
web.Error(w, 404, err)
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
logs := pm.Logs(name, limit)
web.JSON(w, 200, logs)
}
func (pm *ProfileManager) handleAnalyticsByIDOrName(w http.ResponseWriter, r *http.Request) {
idOrName := r.PathValue("id")
name, err := pm.resolveIDOrName(idOrName)
if err != nil {
web.Error(w, 404, err)
return
}
report := pm.Analytics(name)
web.JSON(w, 200, report)
}