307 lines
7.3 KiB
Go
307 lines
7.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"mime"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"infinite-canvas/backend/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) {
|
|
r.POST("/skills/install", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, service.SkillPackageUploadMaxBytes)
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
failService(c, service.BadAuthRequest("请选择 Markdown 或 ZIP 技能文件"))
|
|
return
|
|
}
|
|
isPrivate, err := parseOptionalBool(c.PostForm("isPrivate"))
|
|
if err != nil {
|
|
failService(c, service.BadAuthRequest("isPrivate 必须是布尔值"))
|
|
return
|
|
}
|
|
sourceType := strings.ToLower(strings.TrimSpace(c.PostForm("sourceType")))
|
|
if sourceType == "" {
|
|
switch strings.ToLower(filepath.Ext(file.Filename)) {
|
|
case ".md", ".markdown":
|
|
sourceType = "markdown"
|
|
case ".zip":
|
|
sourceType = "zip"
|
|
}
|
|
}
|
|
skill, err := svc.InstallSkillUpload(user.ID, sourceType, file, service.SkillInstallRequest{
|
|
Name: c.PostForm("name"), Description: c.PostForm("description"), Tag: c.PostForm("tag"), IsPrivate: isPrivate,
|
|
})
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.POST("/skills/install/github", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 32<<10)
|
|
var req service.SkillGitHubInstallRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
failService(c, service.BadAuthRequest("GitHub 技能数据格式无效"))
|
|
return
|
|
}
|
|
skill, err := svc.InstallGitHubSkill(user.ID, req)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.GET("/skills", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
page, pageSize, err := parsePaginationQuery(c, 20)
|
|
if err != nil {
|
|
fail(c, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
result, err := svc.Skills(user.ID, service.SkillListRequest{
|
|
Page: page, PageSize: pageSize, Scope: c.DefaultQuery("scope", "public"),
|
|
Search: c.Query("search"), Tag: c.Query("tag"), Sort: c.DefaultQuery("sort", "popular"),
|
|
})
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, result)
|
|
})
|
|
|
|
r.GET("/skills/added", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
skills, err := svc.AddedSkills(user.ID)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skills": skills})
|
|
})
|
|
|
|
r.GET("/skills/:id", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
skill, err := svc.SkillDetail(user.ID, c.Param("id"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.GET("/skills/:id/files", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
files, err := svc.SkillPackageFiles(user.ID, c.Param("id"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"files": files})
|
|
})
|
|
|
|
r.GET("/skills/:id/file", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
file, err := svc.SkillPackageFile(user.ID, c.Param("id"), c.Query("path"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"file": file})
|
|
})
|
|
|
|
r.GET("/skills/:id/file/raw", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
data, mimeType, fileName, err := svc.SkillPackageRawFile(user.ID, c.Param("id"), c.Query("path"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "private, no-store")
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
c.Header("Content-Security-Policy", "default-src 'none'; sandbox")
|
|
c.Header("Content-Disposition", mime.FormatMediaType("inline", map[string]string{"filename": fileName}))
|
|
c.Data(http.StatusOK, mimeType, data)
|
|
})
|
|
|
|
r.GET("/skills/:id/bundle", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
bundle, err := svc.SkillPackageBundle(user.ID, c.Param("id"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
c.Header("Cache-Control", "private, no-store")
|
|
ok(c, gin.H{"bundle": bundle})
|
|
})
|
|
|
|
r.GET("/skills/:id/search", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
results, err := svc.SearchSkillPackage(user.ID, c.Param("id"), c.Query("q"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"results": results})
|
|
})
|
|
|
|
r.POST("/skills/:id/sync", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
skill, err := svc.SyncGitHubSkill(user.ID, c.Param("id"))
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.POST("/skills", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
var req service.SkillMutationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
failService(c, service.BadAuthRequest("技能数据格式无效"))
|
|
return
|
|
}
|
|
skill, err := svc.CreateSkill(user.ID, req)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.PUT("/skills/:id", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
var req service.SkillMutationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
failService(c, service.BadAuthRequest("技能数据格式无效"))
|
|
return
|
|
}
|
|
skill, err := svc.UpdateSkill(user.ID, c.Param("id"), req)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
})
|
|
|
|
r.DELETE("/skills/:id", func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
if err := svc.DeleteSkill(user.ID, c.Param("id")); err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"deleted": true})
|
|
})
|
|
|
|
r.POST("/skills/:id/add", setSkillAdded(svc, true))
|
|
r.DELETE("/skills/:id/add", setSkillAdded(svc, false))
|
|
r.POST("/skills/:id/like", setSkillLiked(svc, true))
|
|
r.DELETE("/skills/:id/like", setSkillLiked(svc, false))
|
|
}
|
|
|
|
func parseOptionalBool(value string) (bool, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return false, nil
|
|
}
|
|
return strconv.ParseBool(value)
|
|
}
|
|
|
|
func setSkillAdded(svc *service.Service, added bool) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
skill, err := svc.SetSkillAdded(user.ID, c.Param("id"), added)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
}
|
|
}
|
|
|
|
func setSkillLiked(svc *service.Service, liked bool) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
user, err := currentUser(c, svc)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
skill, err := svc.SetSkillLiked(user.ID, c.Param("id"), liked)
|
|
if err != nil {
|
|
failService(c, err)
|
|
return
|
|
}
|
|
ok(c, gin.H{"skill": skill})
|
|
}
|
|
}
|