package service import ( "errors" "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") ErrAlreadyPublished = errors.New("script already published") ) type ScriptService struct { db *gorm.DB } func NewScriptService(db *gorm.DB) *ScriptService { return &ScriptService{db: db} } type CreateInput struct { Title string Description string Content string Runtime string ExpiresIn string Publish bool } 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 d time.Duration switch input.ExpiresIn { case "1h": d = 1 * time.Hour case "24h": d = 24 * time.Hour case "7d": d = 7 * 24 * time.Hour case "30d": d = 30 * 24 * time.Hour default: d = 24 * time.Hour } id, err := idgen.GenerateID(8) if err != nil { return nil, err } adminToken, err := idgen.GenerateToken() if err != nil { return nil, err } status := "draft" var publishedAt *time.Time if input.Publish { status = "published" now := time.Now() publishedAt = &now } script := model.Script{ ID: id, Title: input.Title, Description: input.Description, Content: input.Content, Runtime: input.Runtime, AdminToken: adminToken, Status: status, ExpiresAt: time.Now().Add(d), CreatedAt: time.Now(), PublishedAt: publishedAt, } if err := s.db.Create(&script).Error; err != nil { return nil, err } 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 } func (s *ScriptService) GetByID(id string) (*model.Script, error) { var script model.Script err := s.db.Where("id = ? AND expires_at > ?", id, time.Now()).First(&script).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNotFound } return &script, 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 if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNotFound } if err != nil { return nil, err } if script.Status == "published" { return nil, ErrAlreadyPublished } now := time.Now() script.Status = "published" script.PublishedAt = &now if err := s.db.Save(&script).Error; err != nil { return nil, err } return &script, nil } type MarketQuery struct { Page int PerPage int Runtime string Search string } type MarketResult struct { Items []model.Script Total int64 Page int PerPage int } func (s *ScriptService) ListMarket(q MarketQuery) (*MarketResult, error) { if q.Page < 1 { q.Page = 1 } if q.PerPage < 1 || q.PerPage > 50 { q.PerPage = 20 } 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.Search != "" { search := "%" + q.Search + "%" query = query.Where("title LIKE ? OR description LIKE ?", search, search) } var total int64 query.Count(&total) var scripts []model.Script err := query.Order("published_at DESC"). Offset((q.Page - 1) * q.PerPage). Limit(q.PerPage). Find(&scripts).Error return &MarketResult{ Items: scripts, Total: total, Page: q.Page, PerPage: q.PerPage, }, err } func (s *ScriptService) Delete(id, token string) error { result := s.db.Where("id = ? AND admin_token = ?", id, token).Delete(&model.Script{}) if result.RowsAffected == 0 { return ErrNotFound } return result.Error } func (s *ScriptService) CleanupExpired() (int64, error) { result := s.db.Where("expires_at <= ?", time.Now()).Delete(&model.Script{}) return result.RowsAffected, result.Error }