From da85e416ea0e3af8f82b5a5eb5733c8fb3dd28f9 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Fri, 23 Sep 2022 16:33:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/app.go | 31 ++++ backend/app/dto/app.go | 11 +- backend/app/model/base.go | 6 +- backend/app/repo/app.go | 18 ++- backend/app/repo/app_detail.go | 24 ++- backend/app/repo/app_tag.go | 8 + backend/app/repo/tag.go | 8 + backend/app/service/app.go | 64 +++++++- backend/go.mod | 1 + backend/go.sum | 2 + backend/router/ro_app.go | 2 + backend/utils/common/common.go | 40 +++++ frontend/src/api/interface/app.ts | 21 +-- frontend/src/api/modules/app.ts | 9 +- frontend/src/lang/modules/zh.ts | 2 + frontend/src/routers/modules/app-store.ts | 11 +- frontend/src/views/app-store/apps.json | 29 ---- .../src/views/app-store/detail/detail.json | 10 -- frontend/src/views/app-store/detail/index.vue | 144 +++++++----------- frontend/src/views/app-store/index.vue | 54 +++---- frontend/src/views/app-store/mysql-3.png | Bin 68145 -> 0 bytes 21 files changed, 299 insertions(+), 196 deletions(-) create mode 100644 backend/utils/common/common.go delete mode 100644 frontend/src/views/app-store/apps.json delete mode 100644 frontend/src/views/app-store/detail/detail.json delete mode 100644 frontend/src/views/app-store/mysql-3.png diff --git a/backend/app/api/v1/app.go b/backend/app/api/v1/app.go index a256b3988..2019f5a06 100644 --- a/backend/app/api/v1/app.go +++ b/backend/app/api/v1/app.go @@ -5,6 +5,7 @@ import ( "github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/constant" "github.com/gin-gonic/gin" + "strconv" ) func (b *BaseApi) AppSearch(c *gin.Context) { @@ -30,3 +31,33 @@ func (b *BaseApi) AppSync(c *gin.Context) { } helper.SuccessWithData(c, "") } + +func (b *BaseApi) GetApp(c *gin.Context) { + idStr := c.Param("id") + u64, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + appDTO, err := appService.GetApp(uint(u64)) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, appDTO) +} +func (b *BaseApi) GetAppDetail(c *gin.Context) { + idStr := c.Param("appid") + u64, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + version := c.Param("version") + appDetailDTO, err := appService.GetAppDetail(uint(u64), version) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, appDetailDTO) +} diff --git a/backend/app/dto/app.go b/backend/app/dto/app.go index 27cc9abe8..aa7ca34fb 100644 --- a/backend/app/dto/app.go +++ b/backend/app/dto/app.go @@ -12,7 +12,12 @@ type AppRes struct { type AppDTO struct { model.App - Tags []model.Tag `json:"tags"` + Versions []string `json:"versions"` + Tags []model.Tag `json:"tags"` +} + +type AppDetailDTO struct { + model.AppDetail } type AppList struct { @@ -53,6 +58,6 @@ type AppFormFields struct { type AppRequest struct { PageInfo - Name string `json:"name"` - Types []string `json:"types"` + Name string `json:"name"` + Tags []string `json:"tags"` } diff --git a/backend/app/model/base.go b/backend/app/model/base.go index c93e3ebf2..69921d41d 100644 --- a/backend/app/model/base.go +++ b/backend/app/model/base.go @@ -3,7 +3,7 @@ package model import "time" type BaseModel struct { - ID uint `gorm:"primarykey;AUTO_INCREMENT"` - CreatedAt time.Time - UpdatedAt time.Time + ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } diff --git a/backend/app/repo/app.go b/backend/app/repo/app.go index f36fa2e22..ce2a8798a 100644 --- a/backend/app/repo/app.go +++ b/backend/app/repo/app.go @@ -11,12 +11,6 @@ import ( type AppRepo struct { } -func (a AppRepo) WithInTypes(types []string) DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("type in (?)", types) - } -} - func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) { var apps []model.App db := global.DB.Model(&model.App{}) @@ -29,6 +23,18 @@ func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, err return count, apps, err } +func (a AppRepo) GetFirst(opts ...DBOption) (model.App, error) { + var app model.App + db := global.DB.Model(&model.App{}) + for _, opt := range opts { + db = opt(db) + } + if err := db.First(&app).Error; err != nil { + return app, err + } + return app, nil +} + func (a AppRepo) BatchCreate(ctx context.Context, apps []*model.App) error { db := ctx.Value("db").(*gorm.DB) return db.Omit(clause.Associations).Create(apps).Error diff --git a/backend/app/repo/app_detail.go b/backend/app/repo/app_detail.go index 31675c7ab..80f58676c 100644 --- a/backend/app/repo/app_detail.go +++ b/backend/app/repo/app_detail.go @@ -3,12 +3,34 @@ package repo import ( "context" "github.com/1Panel-dev/1Panel/app/model" + "github.com/1Panel-dev/1Panel/global" "gorm.io/gorm" ) type AppDetailRepo struct { } +func (a AppDetailRepo) WithVersion(version string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("version = ?", version) + } +} +func (a AppDetailRepo) WithAppId(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id = ?", id) + } +} + +func (a AppDetailRepo) GetAppDetail(opts ...DBOption) (model.AppDetail, error) { + var detail model.AppDetail + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&detail).Error + return detail, err +} + func (a AppDetailRepo) BatchCreate(ctx context.Context, details []*model.AppDetail) error { db := ctx.Value("db").(*gorm.DB) return db.Model(&model.AppDetail{}).Create(&details).Error @@ -19,7 +41,7 @@ func (a AppDetailRepo) DeleteByAppIds(ctx context.Context, appIds []uint) error return db.Where("app_id in (?)", appIds).Delete(&model.AppDetail{}).Error } -func (a AppDetailRepo) GetByAppId(ctx context.Context, appId string) ([]model.AppDetail, error) { +func (a AppDetailRepo) GetByAppId(ctx context.Context, appId uint) ([]model.AppDetail, error) { db := ctx.Value("db").(*gorm.DB) var details []model.AppDetail if err := db.Where("app_id = ?", appId).Find(&details).Error; err != nil { diff --git a/backend/app/repo/app_tag.go b/backend/app/repo/app_tag.go index 3217d6970..81d6a9b36 100644 --- a/backend/app/repo/app_tag.go +++ b/backend/app/repo/app_tag.go @@ -27,3 +27,11 @@ func (a AppTagRepo) GetByAppId(appId uint) ([]model.AppTag, error) { } return appTags, nil } + +func (a AppTagRepo) GetByTagIds(tagIds []uint) ([]model.AppTag, error) { + var appTags []model.AppTag + if err := global.DB.Where("tag_id in (?)", tagIds).Find(&appTags).Error; err != nil { + return nil, err + } + return appTags, nil +} diff --git a/backend/app/repo/tag.go b/backend/app/repo/tag.go index cf73d0b73..541d4b6cb 100644 --- a/backend/app/repo/tag.go +++ b/backend/app/repo/tag.go @@ -35,3 +35,11 @@ func (t TagRepo) GetByIds(ids []uint) ([]model.Tag, error) { } return tags, nil } + +func (t TagRepo) GetByKeys(keys []string) ([]model.Tag, error) { + var tags []model.Tag + if err := global.DB.Where("key in (?)", keys).Find(&tags).Error; err != nil { + return nil, err + } + return tags, nil +} diff --git a/backend/app/service/app.go b/backend/app/service/app.go index aa39fc339..8be73517e 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -7,10 +7,12 @@ import ( "github.com/1Panel-dev/1Panel/app/model" "github.com/1Panel-dev/1Panel/app/repo" "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/utils/common" "golang.org/x/net/context" "os" "path" "reflect" + "sort" ) type AppService struct { @@ -23,8 +25,25 @@ func (a AppService) Page(req dto.AppRequest) (interface{}, error) { if req.Name != "" { opts = append(opts, commonRepo.WithLikeName(req.Name)) } - if len(req.Types) != 0 { - opts = append(opts, appRepo.WithInTypes(req.Types)) + if len(req.Tags) != 0 { + tags, err := tagRepo.GetByKeys(req.Tags) + if err != nil { + return nil, err + } + var tagIds []uint + for _, t := range tags { + tagIds = append(tagIds, t.ID) + } + appTags, err := appTagRepo.GetByTagIds(tagIds) + if err != nil { + return nil, err + } + var appIds []uint + for _, t := range appTags { + appIds = append(appIds, t.AppId) + } + + opts = append(opts, commonRepo.WithIdsIn(appIds)) } var res dto.AppRes total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...) @@ -62,6 +81,47 @@ func (a AppService) Page(req dto.AppRequest) (interface{}, error) { return res, nil } +func (a AppService) GetApp(id uint) (dto.AppDTO, error) { + var appDTO dto.AppDTO + app, err := appRepo.GetFirst(commonRepo.WithByID(id)) + if err != nil { + return appDTO, err + } + appDTO.App = app + details, err := appDetailRepo.GetByAppId(context.WithValue(context.Background(), "db", global.DB), id) + if err != nil { + return appDTO, err + } + var versionsRaw []string + for _, detail := range details { + versionsRaw = append(versionsRaw, detail.Version) + } + + sort.Slice(versionsRaw, func(i, j int) bool { + return common.CompareVersion(versionsRaw[i], versionsRaw[j]) + }) + appDTO.Versions = versionsRaw + + return appDTO, nil +} + +func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO, error) { + + var ( + appDetailDTO dto.AppDetailDTO + ) + + var opts []repo.DBOption + opts = append(opts, appDetailRepo.WithAppId(appId), appDetailRepo.WithVersion(version)) + detail, err := appDetailRepo.GetAppDetail(opts...) + if err != nil { + return appDetailDTO, err + } + + appDetailDTO.AppDetail = detail + return appDetailDTO, nil +} + func (a AppService) Sync() error { //TODO 从 oss 拉取最新列表 var appConfig model.AppConfig diff --git a/backend/go.mod b/backend/go.mod index 1b961c284..c857d7cea 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -76,6 +76,7 @@ require ( github.com/google/flatbuffers v1.12.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect diff --git a/backend/go.sum b/backend/go.sum index d3c5664f6..eb6ad32df 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -247,6 +247,8 @@ github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= github.com/gwatts/gin-adapter v1.0.0 h1:TsmmhYTR79/RMTsfYJ2IQvI1F5KZ3ZFJxuQSYEOpyIA= github.com/gwatts/gin-adapter v1.0.0/go.mod h1:44AEV+938HsS0mjfXtBDCUZS9vONlF2gwvh8wu4sRYc= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= diff --git a/backend/router/ro_app.go b/backend/router/ro_app.go index 9663adc84..cf16dd685 100644 --- a/backend/router/ro_app.go +++ b/backend/router/ro_app.go @@ -17,5 +17,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) { { appRouter.POST("/sync", baseApi.AppSync) appRouter.POST("/search", baseApi.AppSearch) + appRouter.GET("/:id", baseApi.GetApp) + appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail) } } diff --git a/backend/utils/common/common.go b/backend/utils/common/common.go new file mode 100644 index 000000000..9ce9eb52c --- /dev/null +++ b/backend/utils/common/common.go @@ -0,0 +1,40 @@ +package common + +import ( + "regexp" + "strconv" + "strings" +) + +func CompareVersion(version1 string, version2 string) bool { + version1s := strings.Split(version1, ".") + version2s := strings.Split(version2, ".") + + n := min(len(version1s), len(version2s)) + re := regexp.MustCompile("[0-9]+") + for i := 0; i < n; i++ { + sVersion1s := re.FindAllString(version1s[i], -1) + sVersion2s := re.FindAllString(version2s[i], -1) + if len(sVersion1s) == 0 { + return false + } + if len(sVersion2s) == 0 { + return false + } + v1num, _ := strconv.Atoi(sVersion1s[0]) + v2num, _ := strconv.Atoi(sVersion2s[0]) + if v1num == v2num { + continue + } else { + return v1num > v2num + } + } + return true +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index 5b961ba81..c3565ca26 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -1,7 +1,7 @@ -import { ReqPage } from '.'; +import { ReqPage, CommonModel } from '.'; export namespace App { - export interface App { + export interface App extends CommonModel { name: string; icon: string; key: string; @@ -12,6 +12,10 @@ export namespace App { type: string; } + export interface AppDTO extends App { + versions: string[]; + } + export interface Tag { key: string; name: string; @@ -25,18 +29,17 @@ export namespace App { tags: App.Tag[]; } - export interface AppDetail { - name: string; + export interface AppDetail extends CommonModel { + appId: string; icon: string; - description: string; - sourceLink: string; - versions: string[]; + version: string; readme: string; - athor: string; + formFields: string; + dockerCompose: string; } export interface AppReq extends ReqPage { name: string; - types: string[]; + tags: string[]; } } diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index edadc7717..34bb645cf 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -1,4 +1,3 @@ -// export const GetAppList = () import http from '@/api'; import { App } from '../interface/app'; @@ -9,3 +8,11 @@ export const SyncApp = () => { export const SearchApp = (req: App.AppReq) => { return http.post('apps/search', req); }; + +export const GetApp = (id: number) => { + return http.get('apps/' + id); +}; + +export const GetAppDetail = (id: number, version: string) => { + return http.get('apps/detail/' + id + '/' + version); +}; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 559903c3c..3813372df 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -392,5 +392,7 @@ export default { installed: '已安装', all: '全部', version: '版本', + detail: '详情', + install: '安装', }, }; diff --git a/frontend/src/routers/modules/app-store.ts b/frontend/src/routers/modules/app-store.ts index 3814cce92..940083e17 100644 --- a/frontend/src/routers/modules/app-store.ts +++ b/frontend/src/routers/modules/app-store.ts @@ -16,17 +16,8 @@ const appStoreRouter = { component: () => import('@/views/app-store/index.vue'), meta: {}, }, - // { - // path: '/apps/detail/:name', - // name: 'AppDetail', - // component: () => import('@/views/app-store/detail/index.vue'), - // meta: { - // hidden: true, - // title: 'menu.apps', - // }, - // }, { - path: '/apps/detail/:name', + path: '/apps/detail/:id', name: 'AppDetail', props: true, hidden: true, diff --git a/frontend/src/views/app-store/apps.json b/frontend/src/views/app-store/apps.json deleted file mode 100644 index 9cb39fc16..000000000 --- a/frontend/src/views/app-store/apps.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "data": [ - { - "name": "Mysql", - "icon": "mysql.png", - "description": "常用的关系型数据库", - "tags": ["数据库"] - }, - { - "name": "Redis", - "icon": "redis.png", - "description": "缓存数据库", - "tags": ["数据库"] - }, - { - "name": "Wordpress", - "icon": "wordpress.png", - "description": "老牌博客平台", - "tags": ["网站"] - }, - { - "name": "Halo", - "icon": "halo.png", - "description": "现代化的博客平台", - "tags": ["网站"] - } - ], - "tags": ["数据库", "网站", "测试", "开发"] -} diff --git a/frontend/src/views/app-store/detail/detail.json b/frontend/src/views/app-store/detail/detail.json deleted file mode 100644 index 7ae140606..000000000 --- a/frontend/src/views/app-store/detail/detail.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Halo", - "description": "更好用的博客模版", - "versions": ["0.0.1", "0.0.2"], - "sourceLink": "", - "athor": "halo", - "status": "", - "readme": "", - "icon": "halo.png" -} diff --git a/frontend/src/views/app-store/detail/index.vue b/frontend/src/views/app-store/detail/index.vue index 8483e08d5..25a79cb6a 100644 --- a/frontend/src/views/app-store/detail/index.vue +++ b/frontend/src/views/app-store/detail/index.vue @@ -1,138 +1,96 @@