108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package job
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"hyapp-admin-server/internal/config"
|
|
"hyapp-admin-server/internal/model"
|
|
"hyapp-admin-server/internal/modules/shared"
|
|
"hyapp-admin-server/internal/repository"
|
|
)
|
|
|
|
const userExportJobType = "user-export"
|
|
|
|
type JobService struct {
|
|
store *repository.Store
|
|
cfg config.Config
|
|
}
|
|
|
|
func NewService(store *repository.Store, cfg config.Config) *JobService {
|
|
return &JobService{store: store, cfg: cfg}
|
|
}
|
|
|
|
func (s *JobService) ListJobs(options repository.ListOptions) ([]model.AdminJob, int64, error) {
|
|
return s.store.ListJobs(options)
|
|
}
|
|
|
|
func (s *JobService) GetJob(id uint) (*model.AdminJob, error) {
|
|
return s.store.FindJobByID(id)
|
|
}
|
|
|
|
func (s *JobService) CreateUserExportJob(actor shared.Actor, options repository.ListOptions) (*model.AdminJob, error) {
|
|
payload, err := json.Marshal(options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
job := model.AdminJob{
|
|
Type: userExportJobType,
|
|
Status: model.JobStatusPending,
|
|
PayloadJSON: string(payload),
|
|
MaxAttempts: s.cfg.Jobs.MaxAttempts,
|
|
CreatedBy: actor.UserID,
|
|
CreatedByName: actor.Username,
|
|
}
|
|
if job.MaxAttempts <= 0 {
|
|
job.MaxAttempts = 3
|
|
}
|
|
if err := s.store.CreateJob(&job); err != nil {
|
|
return nil, err
|
|
}
|
|
return &job, nil
|
|
}
|
|
|
|
func (s *JobService) CancelJob(id uint) error {
|
|
return s.store.CancelJob(id)
|
|
}
|
|
|
|
func (s *JobService) JobArtifact(id uint) (*model.AdminJob, string, error) {
|
|
job, err := s.store.FindJobByID(id)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if job.Status != model.JobStatusSucceeded || strings.TrimSpace(job.ArtifactPath) == "" {
|
|
return nil, "", errors.New("job artifact is not ready")
|
|
}
|
|
artifactPath, err := safeArtifactPath(s.cfg.Jobs.ArtifactDir, job.ArtifactPath)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if _, err := os.Stat(artifactPath); err != nil {
|
|
return nil, "", err
|
|
}
|
|
return job, artifactPath, nil
|
|
}
|
|
|
|
func safeArtifactPath(root string, artifactPath string) (string, error) {
|
|
root = strings.TrimSpace(root)
|
|
if root == "" {
|
|
root = "storage/exports"
|
|
}
|
|
cleanRoot, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cleanArtifact := filepath.Clean(strings.TrimSpace(artifactPath))
|
|
if filepath.IsAbs(cleanArtifact) {
|
|
cleanArtifact, err = filepath.Abs(cleanArtifact)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if cleanArtifact == cleanRoot || strings.HasPrefix(cleanArtifact, cleanRoot+string(os.PathSeparator)) {
|
|
return cleanArtifact, nil
|
|
}
|
|
return "", errors.New("invalid artifact path")
|
|
}
|
|
fullPath, err := filepath.Abs(filepath.Join(cleanRoot, cleanArtifact))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if fullPath != cleanRoot && !strings.HasPrefix(fullPath, cleanRoot+string(os.PathSeparator)) {
|
|
return "", errors.New("invalid artifact path")
|
|
}
|
|
return fullPath, nil
|
|
}
|