package handler import ( "errors" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "gitea.kmux.cn/zhilv/scriptforge/internal/auth" "gitea.kmux.cn/zhilv/scriptforge/internal/model" "gitea.kmux.cn/zhilv/scriptforge/internal/service" ) func getUserID(c *gin.Context) uint { val, exists := c.Get("user_id") if !exists { return 0 } switch v := val.(type) { case uint: return v case float64: return uint(v) default: return 0 } } // --- Auth handlers --- type registerRequest struct { Username string `json:"username" binding:"required,min=3,max=32"` Password string `json:"password" binding:"required,min=6"` } func Register(authSvc *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { var req registerRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, err := authSvc.Register(req.Username, req.Password) if errors.Is(err, auth.ErrUserExists) { c.JSON(http.StatusConflict, gin.H{"error": "username already exists"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } token, _ := authSvc.GenerateToken(user.ID, user.Username, user.Role) c.JSON(http.StatusCreated, gin.H{ "id": user.ID, "username": user.Username, "role": user.Role, "token": token, }) } } func Login(authSvc *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { var req registerRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, token, err := authSvc.Login(req.Username, req.Password) if errors.Is(err, auth.ErrInvalidCredentials) { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusOK, gin.H{ "id": user.ID, "username": user.Username, "role": user.Role, "token": token, }) } } func GetMe(authSvc *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { userID := getUserID(c) if userID == 0 { c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"}) return } user, err := authSvc.GetUserByID(userID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } c.JSON(http.StatusOK, gin.H{ "id": user.ID, "username": user.Username, "role": user.Role, }) } } // --- Script handlers --- type createRequest struct { Title string `json:"title" binding:"required,max=128"` Description string `json:"description" binding:"max=512"` Content string `json:"content" binding:"required,max=16384"` CategoryID uint `json:"category_id" binding:"required"` ExpiresIn string `json:"expires_in" binding:"required,oneof=1h 24h 7d 30d"` Publish bool `json:"publish"` } func CreateScript(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { var req createRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } scheme := "https" if strings.HasPrefix(c.Request.Host, "localhost") || strings.HasPrefix(c.Request.Host, "127.0.0.1") { scheme = "http" } userID := getUserID(c) result, err := svc.Create(service.CreateInput{ Title: req.Title, Description: req.Description, Content: req.Content, CategoryID: req.CategoryID, ExpiresIn: req.ExpiresIn, Publish: req.Publish, UserID: userID, }, scheme, c.Request.Host) if errors.Is(err, service.ErrInvalidCategory) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid category"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusCreated, gin.H{ "id": result.Script.ID, "title": result.Script.Title, "description": result.Script.Description, "category_id": result.Script.CategoryID, "admin_token": result.AdminToken, "url": result.RawURL, "status": result.Script.Status, "expires_at": result.Script.ExpiresAt, }) } } func GetScript(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") script, err := svc.GetByID(id) if errors.Is(err, service.ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "script not found or expired"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } cat, _ := svc.GetCategoryByID(script.CategoryID) variants, _ := svc.GetVariants(script.CategoryID) scheme := "https" if strings.HasPrefix(c.Request.Host, "localhost") || strings.HasPrefix(c.Request.Host, "127.0.0.1") { scheme = "http" } fullURL := scheme + "://" + c.Request.Host + "/raw/" + script.ID var commands []gin.H for _, v := range variants { cmd := strings.ReplaceAll(v.CommandTemplate, "{url}", fullURL) entry := gin.H{ "variant": v.Name, "label": v.Label, "command": cmd, } if v.SourceTemplate != "" { entry["source_command"] = strings.ReplaceAll(v.SourceTemplate, "{url}", fullURL) } commands = append(commands, entry) } categoryName := "" categoryLabel := "" categoryIcon := "" if cat != nil { categoryName = cat.Name categoryLabel = cat.Label categoryIcon = cat.Icon } c.JSON(http.StatusOK, gin.H{ "id": script.ID, "title": script.Title, "description": script.Description, "category_id": script.CategoryID, "category_name": categoryName, "category_label": categoryLabel, "category_icon": categoryIcon, "content": script.Content, "content_length": len(script.Content), "status": script.Status, "commands": commands, "expires_at": script.ExpiresAt, "published_at": script.PublishedAt, "expired": false, }) } } func PublishScript(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") token := c.Query("token") if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "token required"}) return } script, err := svc.Publish(id, token) if errors.Is(err, service.ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "not found or invalid token"}) return } if errors.Is(err, service.ErrAlreadyPublished) { c.JSON(http.StatusBadRequest, gin.H{"error": "already published"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusOK, gin.H{"id": script.ID, "status": script.Status, "published_at": script.PublishedAt}) } } func DeleteScript(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") token := c.Query("token") if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "token required"}) return } if err := svc.Delete(id, token); errors.Is(err, service.ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.Status(http.StatusNoContent) } } func ListMarket(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) catID, _ := strconv.Atoi(c.DefaultQuery("category_id", "0")) search := c.Query("search") result, err := svc.ListMarket(service.MarketQuery{ Page: page, PerPage: perPage, CategoryID: uint(catID), Search: search, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } items := make([]gin.H, len(result.Items)) for i, s := range result.Items { cat, _ := svc.GetCategoryByID(s.CategoryID) items[i] = gin.H{ "id": s.ID, "title": s.Title, "description": s.Description, "category_id": s.CategoryID, "category_name": cat.Name, "category_label": cat.Label, "category_icon": cat.Icon, "published_at": s.PublishedAt, } } c.JSON(http.StatusOK, gin.H{ "items": items, "total": result.Total, "page": result.Page, "per_page": result.PerPage, }) } } // --- Admin: Runtime management --- func ListCategories(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { cats, err := svc.ListCategories() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusOK, gin.H{"categories": cats}) } } func CreateCategory(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { var cat model.RuntimeCategory if err := c.ShouldBindJSON(&cat); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := svc.CreateCategory(&cat); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusCreated, gin.H{"category": cat}) } } func UpdateCategory(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var cat model.RuntimeCategory cat.ID = uint(id) if err := c.ShouldBindJSON(&cat); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := svc.UpdateCategory(&cat); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusOK, gin.H{"category": cat}) } } func DeleteCategory(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := svc.DeleteCategory(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.Status(http.StatusNoContent) } } func CreateVariant(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { var v model.RuntimeVariant if err := c.ShouldBindJSON(&v); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := svc.CreateVariant(&v); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusCreated, gin.H{"variant": v}) } } func UpdateVariant(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var v model.RuntimeVariant v.ID = uint(id) if err := c.ShouldBindJSON(&v); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := svc.UpdateVariant(&v); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.JSON(http.StatusOK, gin.H{"variant": v}) } } func DeleteVariant(svc *service.ScriptService) gin.HandlerFunc { return func(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) if err := svc.DeleteVariant(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) return } c.Status(http.StatusNoContent) } }