diff --git a/internal/biz/app.go b/internal/biz/app.go index e42f2161..564f1e2a 100644 --- a/internal/biz/app.go +++ b/internal/biz/app.go @@ -7,26 +7,28 @@ import ( ) type App struct { - ID uint `gorm:"primaryKey" json:"id"` - Slug string `gorm:"not null;unique" json:"slug"` - Version string `gorm:"not null" json:"version"` - Show bool `gorm:"not null" json:"show"` - ShowOrder int `gorm:"not null" json:"show_order"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primaryKey" json:"id"` + Slug string `gorm:"not null;unique" json:"slug"` + VersionSlug string `gorm:"not null" json:"version_slug"` + Version string `gorm:"not null" json:"version"` + Show bool `gorm:"not null" json:"show"` + ShowOrder int `gorm:"not null" json:"show_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type AppRepo interface { All() api.Apps Get(slug string) (*api.App, error) + UpdateExist(slug string) bool Installed() ([]*App, error) GetInstalled(slug string) (*App, error) GetInstalledAll(query string, cond ...string) ([]*App, error) GetHomeShow() ([]map[string]string, error) IsInstalled(query string, cond ...string) (bool, error) - Install(slug string) error - Uninstall(slug string) error - Update(slug string) error + Install(slug, versionSlug string) error + Uninstall(slug, versionSlug string) error + Update(slug, versionSlug string) error UpdateShow(slug string, show bool) error UpdateCache() error } diff --git a/internal/data/app.go b/internal/data/app.go index d032cd00..eed25f42 100644 --- a/internal/data/app.go +++ b/internal/data/app.go @@ -7,12 +7,14 @@ import ( "slices" "github.com/expr-lang/expr" + "github.com/hashicorp/go-version" "github.com/spf13/cast" "github.com/TheTNB/panel/internal/app" "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/pkg/api" "github.com/TheTNB/panel/pkg/apploader" + "github.com/TheTNB/panel/pkg/str" ) type appRepo struct { @@ -42,14 +44,36 @@ func (r *appRepo) All() api.Apps { } func (r *appRepo) Get(slug string) (*api.App, error) { - for app := range slices.Values(r.All()) { - if app.Slug == slug { - return app, nil + for item := range slices.Values(r.All()) { + if item.Slug == slug { + return item, nil } } return nil, errors.New("应用不存在") } +func (r *appRepo) UpdateExist(slug string) bool { + item, err := r.Get(slug) + if err != nil { + return false + } + installed, err := r.GetInstalled(slug) + if err != nil { + return false + } + + for v := range slices.Values(item.Versions) { + if v.Slug == installed.VersionSlug { + current := str.FirstElement(v.Subs) + if current != nil && current.Version != installed.Version { + return true + } + } + } + + return false +} + func (r *appRepo) Installed() ([]*biz.App, error) { var apps []*biz.App if err := app.Orm.Find(&apps).Error; err != nil { @@ -117,20 +141,31 @@ func (r *appRepo) IsInstalled(query string, cond ...string) (bool, error) { return count > 0, nil } -func (r *appRepo) Install(slug string) error { +func (r *appRepo) Install(slug, versionSlug string) error { item, err := r.Get(slug) if err != nil { return err } + panel, err := version.NewVersion(app.Version) + if err != nil { + return err + } if installed, _ := r.IsInstalled(slug); installed { return errors.New("应用已安装") } var shellUrl string - for version := range slices.Values(item.Versions) { - if version.PanelVersion == app.Version { - shellUrl = version.Install + for v := range slices.Values(item.Versions) { + vs, err := version.NewVersion(v.Panel) + if err != nil { + continue + } + if v.Slug == versionSlug { + if vs.GreaterThan(panel) { + return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, v.Panel, app.Version) + } + shellUrl = v.Install break } } @@ -154,26 +189,34 @@ func (r *appRepo) Install(slug string) error { return err } -func (r *appRepo) Uninstall(slug string) error { +func (r *appRepo) Uninstall(slug, versionSlug string) error { item, err := r.Get(slug) if err != nil { return err } + panel, err := version.NewVersion(app.Version) + if err != nil { + return err + } if installed, _ := r.IsInstalled(slug); !installed { return errors.New("应用未安装") } var shellUrl string - for version := range slices.Values(item.Versions) { - if version.PanelVersion == app.Version { - shellUrl = version.Uninstall + for v := range slices.Values(item.Versions) { + vs, err := version.NewVersion(v.Panel) + if err != nil { + continue + } + if v.Slug == versionSlug { + if vs.GreaterThan(panel) { + return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, v.Panel, app.Version) + } + shellUrl = v.Uninstall break } } - if shellUrl == "" && len(item.Versions) > 0 { - shellUrl = item.Versions[0].Uninstall - } if shellUrl == "" { return fmt.Errorf("无法获取应用 %s 的卸载脚本", item.Name) } @@ -194,20 +237,31 @@ func (r *appRepo) Uninstall(slug string) error { return err } -func (r *appRepo) Update(slug string) error { +func (r *appRepo) Update(slug, versionSlug string) error { item, err := r.Get(slug) if err != nil { return err } + panel, err := version.NewVersion(app.Version) + if err != nil { + return err + } if installed, _ := r.IsInstalled(slug); !installed { return errors.New("应用未安装") } var shellUrl string - for version := range slices.Values(item.Versions) { - if version.PanelVersion == app.Version { - shellUrl = version.Update + for v := range slices.Values(item.Versions) { + vs, err := version.NewVersion(v.Panel) + if err != nil { + continue + } + if v.Slug == versionSlug { + if vs.GreaterThan(panel) { + return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, v.Panel, app.Version) + } + shellUrl = v.Update break } } diff --git a/internal/http/request/app.go b/internal/http/request/app.go index eac27701..908c934b 100644 --- a/internal/http/request/app.go +++ b/internal/http/request/app.go @@ -1,5 +1,10 @@ package request +type App struct { + Slug string `json:"slug" form:"slug"` + VersionSlug string `json:"version_slug" form:"version_slug"` +} + type AppSlug struct { Slug string `json:"slug" form:"slug"` } diff --git a/internal/service/app.go b/internal/service/app.go index b40e1699..ee3b651b 100644 --- a/internal/service/app.go +++ b/internal/service/app.go @@ -8,7 +8,6 @@ import ( "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/internal/data" "github.com/TheTNB/panel/internal/http/request" - "github.com/TheTNB/panel/pkg/str" "github.com/TheTNB/panel/pkg/types" ) @@ -37,23 +36,24 @@ func (s *AppService) List(w http.ResponseWriter, r *http.Request) { var apps []types.StoreApp for _, item := range all { - installed, installedVersion, currentVersion, show := false, "", "", false - if str.FirstElement(item.Versions) != nil { - currentVersion = str.FirstElement(item.Versions).Version - } + installed, installedVersion, installedVersionSlug, updateExist, show := false, "", "", false, false if _, ok := installedAppMap[item.Slug]; ok { installed = true installedVersion = installedAppMap[item.Slug].Version + installedVersionSlug = installedAppMap[item.Slug].VersionSlug + updateExist = s.appRepo.UpdateExist(item.Slug) show = installedAppMap[item.Slug].Show } apps = append(apps, types.StoreApp{ - Name: item.Name, - Description: item.Description, - Slug: item.Slug, - Version: currentVersion, - Installed: installed, - InstalledVersion: installedVersion, - Show: show, + Name: item.Name, + Description: item.Description, + Slug: item.Slug, + Versions: item.Versions, + Installed: installed, + InstalledVersion: installedVersion, + InstalledVersionSlug: installedVersionSlug, + UpdateExist: updateExist, + Show: show, }) } @@ -66,13 +66,13 @@ func (s *AppService) List(w http.ResponseWriter, r *http.Request) { } func (s *AppService) Install(w http.ResponseWriter, r *http.Request) { - req, err := Bind[request.AppSlug](r) + req, err := Bind[request.App](r) if err != nil { Error(w, http.StatusUnprocessableEntity, err.Error()) return } - if err = s.appRepo.Install(req.Slug); err != nil { + if err = s.appRepo.Install(req.Slug, req.VersionSlug); err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } @@ -81,13 +81,13 @@ func (s *AppService) Install(w http.ResponseWriter, r *http.Request) { } func (s *AppService) Uninstall(w http.ResponseWriter, r *http.Request) { - req, err := Bind[request.AppSlug](r) + req, err := Bind[request.App](r) if err != nil { Error(w, http.StatusUnprocessableEntity, err.Error()) return } - if err = s.appRepo.Uninstall(req.Slug); err != nil { + if err = s.appRepo.Uninstall(req.Slug, req.VersionSlug); err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } @@ -96,13 +96,13 @@ func (s *AppService) Uninstall(w http.ResponseWriter, r *http.Request) { } func (s *AppService) Update(w http.ResponseWriter, r *http.Request) { - req, err := Bind[request.AppSlug](r) + req, err := Bind[request.App](r) if err != nil { Error(w, http.StatusUnprocessableEntity, err.Error()) return } - if err = s.appRepo.Update(req.Slug); err != nil { + if err = s.appRepo.Update(req.Slug, req.VersionSlug); err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } diff --git a/pkg/api/app.go b/pkg/api/app.go index eff08b21..8564a8ef 100644 --- a/pkg/api/app.go +++ b/pkg/api/app.go @@ -15,11 +15,16 @@ type App struct { Categories []string `json:"categories"` Depends string `json:"depends"` Versions []struct { - Version string `json:"version"` - Install string `json:"install"` - Uninstall string `json:"uninstall"` - Update string `json:"update"` - PanelVersion string `json:"panel_version"` + Slug string `json:"slug"` + Name string `json:"name"` + Panel string `json:"panel"` + Install string `json:"install"` + Uninstall string `json:"uninstall"` + Update string `json:"update"` + Subs []struct { + Log string `json:"log"` + Version string `json:"version"` + } `json:"versions"` } `json:"versions"` Order int `json:"order"` } diff --git a/pkg/types/app.go b/pkg/types/app.go index 7512f3f3..62697abe 100644 --- a/pkg/types/app.go +++ b/pkg/types/app.go @@ -10,11 +10,24 @@ type App struct { // StoreApp 商店应用结构 type StoreApp struct { - Name string `json:"name"` - Description string `json:"description"` - Slug string `json:"slug"` - Version string `json:"version"` - Installed bool `json:"installed"` - InstalledVersion string `json:"installed_version"` - Show bool `json:"show"` + Name string `json:"name"` + Description string `json:"description"` + Slug string `json:"slug"` + Versions []struct { + Slug string `json:"slug"` + Name string `json:"name"` + Panel string `json:"panel"` + Install string `json:"install"` + Uninstall string `json:"uninstall"` + Update string `json:"update"` + Subs []struct { + Log string `json:"log"` + Version string `json:"version"` + } `json:"versions"` + } `json:"versions"` + Installed bool `json:"installed"` + InstalledVersion string `json:"installed_version"` + InstalledVersionSlug string `json:"installed_version_slug"` + UpdateExist bool `json:"update_exist"` + Show bool `json:"show"` }