feat: 分类/变体体系 + 用户认证 + 管理后台
- 运行时分类体系:Shell/Python/JavaScript/Ruby/PHP 各含变体 - 用户注册/登录(JWT + bcrypt),首个注册用户为管理员 - 管理后台 /admin 动态管理分类和变体 - 脚本市场支持按分类筛选 - CodeMirror 语言模式根据分类名称自动切换 - 结果页展示该分类下所有变体的运行命令 - source 命令变体用于 Shell 类继承环境变量 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/assets"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/auth"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/handler"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/middleware"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/model"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/seed"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/service"
|
||||
)
|
||||
|
||||
@@ -26,13 +28,17 @@ func main() {
|
||||
|
||||
db := initDB()
|
||||
syncDB(db)
|
||||
seed.Run(db)
|
||||
|
||||
svc := service.NewScriptService(db)
|
||||
authSvc := auth.NewAuthService(db, getJWTKey())
|
||||
|
||||
go cleanupLoop(svc)
|
||||
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery(), middleware.Logger())
|
||||
|
||||
// Public API
|
||||
api := r.Group("/api")
|
||||
{
|
||||
api.Use(middleware.RateLimit(60))
|
||||
@@ -41,6 +47,33 @@ func main() {
|
||||
api.POST("/scripts/:id/publish", handler.PublishScript(svc))
|
||||
api.DELETE("/scripts/:id", handler.DeleteScript(svc))
|
||||
api.GET("/market", handler.ListMarket(svc))
|
||||
|
||||
// Auth
|
||||
api.POST("/auth/register", handler.Register(authSvc))
|
||||
api.POST("/auth/login", handler.Login(authSvc))
|
||||
|
||||
// Categories (public read)
|
||||
api.GET("/categories", handler.ListCategories(svc))
|
||||
}
|
||||
|
||||
// Authenticated API
|
||||
authGroup := r.Group("/api")
|
||||
authGroup.Use(middleware.JWTAuth(authSvc))
|
||||
{
|
||||
authGroup.GET("/auth/me", handler.GetMe(authSvc))
|
||||
}
|
||||
|
||||
// Admin API
|
||||
adminGroup := r.Group("/api/admin")
|
||||
adminGroup.Use(middleware.JWTAuth(authSvc), middleware.AdminOnly(authSvc))
|
||||
{
|
||||
adminGroup.GET("/categories", handler.ListCategories(svc))
|
||||
adminGroup.POST("/categories", handler.CreateCategory(svc))
|
||||
adminGroup.PUT("/categories/:id", handler.UpdateCategory(svc))
|
||||
adminGroup.DELETE("/categories/:id", handler.DeleteCategory(svc))
|
||||
adminGroup.POST("/categories/:id/variants", handler.CreateVariant(svc))
|
||||
adminGroup.PUT("/variants/:id", handler.UpdateVariant(svc))
|
||||
adminGroup.DELETE("/variants/:id", handler.DeleteVariant(svc))
|
||||
}
|
||||
|
||||
r.GET("/raw/:id", handler.GetRawScript(svc))
|
||||
@@ -77,7 +110,7 @@ func initDB() *gorm.DB {
|
||||
}
|
||||
|
||||
func syncDB(db *gorm.DB) {
|
||||
if err := db.AutoMigrate(&model.Script{}); err != nil {
|
||||
if err := db.AutoMigrate(&model.User{}, &model.RuntimeCategory{}, &model.RuntimeVariant{}, &model.Script{}); err != nil {
|
||||
log.Fatalf("failed to migrate database: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -93,6 +126,14 @@ func cleanupLoop(svc *service.ScriptService) {
|
||||
}
|
||||
}
|
||||
|
||||
func getJWTKey() string {
|
||||
key := os.Getenv("JWT_KEY")
|
||||
if key == "" {
|
||||
key = "scriptforge-dev-key-change-in-production"
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func getPort() string {
|
||||
if p := os.Getenv("PORT"); p != "" {
|
||||
return p
|
||||
@@ -128,4 +169,4 @@ func serveStaticOrSPA(r *gin.Engine) {
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.FileFromFS("index.html", http.FS(staticFS))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user