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:
@@ -2,19 +2,19 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/config"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/idgen"
|
||||
"gitea.kmux.cn/zhilv/scriptforge/internal/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRuntime = errors.New("invalid runtime")
|
||||
ErrNotFound = errors.New("script not found or expired")
|
||||
ErrNotFound = errors.New("script not found or expired")
|
||||
ErrAlreadyPublished = errors.New("script already published")
|
||||
ErrInvalidCategory = errors.New("invalid category")
|
||||
)
|
||||
|
||||
type ScriptService struct {
|
||||
@@ -29,22 +29,22 @@ type CreateInput struct {
|
||||
Title string
|
||||
Description string
|
||||
Content string
|
||||
Runtime string
|
||||
CategoryID uint
|
||||
ExpiresIn string
|
||||
Publish bool
|
||||
UserID uint
|
||||
}
|
||||
|
||||
type CreateResult struct {
|
||||
Script model.Script
|
||||
AdminToken string
|
||||
Command string
|
||||
SourceCmd string
|
||||
RawURL string
|
||||
}
|
||||
|
||||
func (s *ScriptService) Create(input CreateInput, scheme, host string) (*CreateResult, error) {
|
||||
if !config.IsValidRuntime(input.Runtime) {
|
||||
return nil, ErrInvalidRuntime
|
||||
var cat model.RuntimeCategory
|
||||
if err := s.db.First(&cat, input.CategoryID).Error; err != nil {
|
||||
return nil, ErrInvalidCategory
|
||||
}
|
||||
|
||||
var d time.Duration
|
||||
@@ -84,11 +84,11 @@ func (s *ScriptService) Create(input CreateInput, scheme, host string) (*CreateR
|
||||
Title: input.Title,
|
||||
Description: input.Description,
|
||||
Content: input.Content,
|
||||
Runtime: input.Runtime,
|
||||
CategoryID: input.CategoryID,
|
||||
UserID: input.UserID,
|
||||
AdminToken: adminToken,
|
||||
Status: status,
|
||||
ExpiresAt: time.Now().Add(d),
|
||||
CreatedAt: time.Now(),
|
||||
PublishedAt: publishedAt,
|
||||
}
|
||||
|
||||
@@ -97,14 +97,10 @@ func (s *ScriptService) Create(input CreateInput, scheme, host string) (*CreateR
|
||||
}
|
||||
|
||||
rawURL := scheme + "://" + host + "/raw/" + id
|
||||
command := "curl " + rawURL + " | " + input.Runtime
|
||||
sourceCmd := config.GetSourceCommand(rawURL, input.Runtime)
|
||||
|
||||
return &CreateResult{
|
||||
Script: script,
|
||||
AdminToken: adminToken,
|
||||
Command: command,
|
||||
SourceCmd: sourceCmd,
|
||||
RawURL: rawURL,
|
||||
}, nil
|
||||
}
|
||||
@@ -118,6 +114,27 @@ func (s *ScriptService) GetByID(id string) (*model.Script, error) {
|
||||
return &script, err
|
||||
}
|
||||
|
||||
func (s *ScriptService) GetCategoryByID(id uint) (*model.RuntimeCategory, error) {
|
||||
var cat model.RuntimeCategory
|
||||
err := s.db.Preload("Variants").First(&cat, id).Error
|
||||
return &cat, err
|
||||
}
|
||||
|
||||
func (s *ScriptService) GetVariants(categoryID uint) ([]model.RuntimeVariant, error) {
|
||||
var variants []model.RuntimeVariant
|
||||
err := s.db.Where("category_id = ?", categoryID).Order("sort_order ASC").Find(&variants).Error
|
||||
return variants, err
|
||||
}
|
||||
|
||||
func (s *ScriptService) GetDefaultVariant(categoryID uint) (*model.RuntimeVariant, error) {
|
||||
var v model.RuntimeVariant
|
||||
err := s.db.Where("category_id = ? AND is_default = true", categoryID).First(&v).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = s.db.Where("category_id = ?", categoryID).Order("sort_order ASC").First(&v).Error
|
||||
}
|
||||
return &v, err
|
||||
}
|
||||
|
||||
func (s *ScriptService) Publish(id, token string) (*model.Script, error) {
|
||||
var script model.Script
|
||||
err := s.db.Where("id = ? AND admin_token = ? AND expires_at > ?", id, token, time.Now()).First(&script).Error
|
||||
@@ -134,7 +151,6 @@ func (s *ScriptService) Publish(id, token string) (*model.Script, error) {
|
||||
now := time.Now()
|
||||
script.Status = "published"
|
||||
script.PublishedAt = &now
|
||||
|
||||
if err := s.db.Save(&script).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,16 +158,16 @@ func (s *ScriptService) Publish(id, token string) (*model.Script, error) {
|
||||
}
|
||||
|
||||
type MarketQuery struct {
|
||||
Page int
|
||||
PerPage int
|
||||
Runtime string
|
||||
Search string
|
||||
Page int
|
||||
PerPage int
|
||||
CategoryID uint
|
||||
Search string
|
||||
}
|
||||
|
||||
type MarketResult struct {
|
||||
Items []model.Script
|
||||
Total int64
|
||||
Page int
|
||||
Items []model.Script
|
||||
Total int64
|
||||
Page int
|
||||
PerPage int
|
||||
}
|
||||
|
||||
@@ -166,8 +182,8 @@ func (s *ScriptService) ListMarket(q MarketQuery) (*MarketResult, error) {
|
||||
query := s.db.Model(&model.Script{}).
|
||||
Where("status = ? AND expires_at > ?", "published", time.Now())
|
||||
|
||||
if q.Runtime != "" && config.IsValidRuntime(q.Runtime) {
|
||||
query = query.Where("runtime = ?", q.Runtime)
|
||||
if q.CategoryID > 0 {
|
||||
query = query.Where("category_id = ?", q.CategoryID)
|
||||
}
|
||||
if q.Search != "" {
|
||||
search := "%" + q.Search + "%"
|
||||
@@ -202,4 +218,39 @@ func (s *ScriptService) Delete(id, token string) error {
|
||||
func (s *ScriptService) CleanupExpired() (int64, error) {
|
||||
result := s.db.Where("expires_at <= ?", time.Now()).Delete(&model.Script{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) ListCategories() ([]model.RuntimeCategory, error) {
|
||||
var cats []model.RuntimeCategory
|
||||
err := s.db.Preload("Variants").Order("sort_order ASC").Find(&cats).Error
|
||||
return cats, err
|
||||
}
|
||||
|
||||
func (s *ScriptService) CreateCategory(cat *model.RuntimeCategory) error {
|
||||
return s.db.Create(cat).Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) UpdateCategory(cat *model.RuntimeCategory) error {
|
||||
return s.db.Save(cat).Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) DeleteCategory(id uint) error {
|
||||
s.db.Where("category_id = ?", id).Delete(&model.RuntimeVariant{})
|
||||
return s.db.Delete(&model.RuntimeCategory{}, id).Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) CreateVariant(v *model.RuntimeVariant) error {
|
||||
return s.db.Create(v).Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) UpdateVariant(v *model.RuntimeVariant) error {
|
||||
return s.db.Save(v).Error
|
||||
}
|
||||
|
||||
func (s *ScriptService) DeleteVariant(id uint) error {
|
||||
return s.db.Delete(&model.RuntimeVariant{}, id).Error
|
||||
}
|
||||
|
||||
func FormatCommand(template, url string) string {
|
||||
return fmt.Sprintf(template, url)
|
||||
}
|
||||
Reference in New Issue
Block a user