diff --git a/api/app_management/openapi.yaml b/api/app_management/openapi.yaml index e20670e5..b7cb71c8 100644 --- a/api/app_management/openapi.yaml +++ b/api/app_management/openapi.yaml @@ -226,6 +226,31 @@ paths: "500": $ref: "#/components/responses/ResponseInternalServerError" + # the endpoint is newer version of POST /appstore for the demand of @ETWang1991 + # the old api is dependent by CasaOS. So we can't remove it. + # a good idea is /v3/app-management/appstore. But it is hard to implement in ZimaOS + # So I had to add the endpoint here. + /appstore/sync: + post: + summary: Register an app store in Sync. + description: | + (TODO) + operationId: registerAppStoreSync + tags: + - AppStore methods + parameters: + - $ref: "#/components/parameters/AppStoreURL" + responses: + "200": + $ref: "#/components/responses/AppStoreRegisterOK" + "400": + $ref: "#/components/responses/ResponseBadRequest" + "409": + $ref: "#/components/responses/ResponseConflict" + "500": + $ref: "#/components/responses/ResponseInternalServerError" + + /appstore: get: summary: Get the list of registered app stores diff --git a/route/v2/appstore.go b/route/v2/appstore.go index 199e74ed..d4c5eca9 100644 --- a/route/v2/appstore.go +++ b/route/v2/appstore.go @@ -30,36 +30,62 @@ func (a *AppManagement) AppStoreList(ctx echo.Context) error { }) } +// the method should be deprecated +// but it be used by CasaOS func (a *AppManagement) RegisterAppStore(ctx echo.Context, params codegen.RegisterAppStoreParams) error { if params.Url == nil || *params.Url == "" { message := "appstore url is required" return ctx.JSON(http.StatusBadRequest, codegen.ResponseBadRequest{Message: &message}) } - isExist := lo.ContainsBy(service.MyService.AppStoreManagement().AppStoreList(), func(appstore codegen.AppStoreMetadata) bool { - return appstore.URL != nil && strings.EqualFold(*appstore.URL, *params.Url) + backgroundCtx := common.WithProperties(context.Background(), PropertiesFromQueryParams(ctx)) + + if err := service.MyService.AppStoreManagement().RegisterAppStore(backgroundCtx, *params.Url); err != nil { + message := err.Error() + + if err != nil { + switch err { + case service.ErrAppStoreSourceExists: + return ctx.JSON(http.StatusConflict, codegen.ResponseConflict{Message: &message}) + case service.ErrNotAppStore: + return ctx.JSON(http.StatusBadRequest, codegen.ResponseBadRequest{Message: &message}) + default: + return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{Message: &message}) + } + } + } + + logFilepath := filepath.Join(config.AppInfo.LogPath, fmt.Sprintf("%s.%s", config.AppInfo.LogSaveName, config.AppInfo.LogFileExt)) + message := fmt.Sprintf("trying to register app store asynchronously - see %s for any errors.", logFilepath) + return ctx.JSON(http.StatusOK, codegen.AppStoreRegisterOK{ + Message: &message, }) +} - if isExist { - message := "appstore is already registered" - return ctx.JSON(http.StatusConflict, codegen.ResponseConflict{Message: &message}) +func (a *AppManagement) RegisterAppStoreSync(ctx echo.Context, params codegen.RegisterAppStoreSyncParams) error { + if params.Url == nil || *params.Url == "" { + message := "appstore url is required" + return ctx.JSON(http.StatusBadRequest, codegen.ResponseBadRequest{Message: &message}) } backgroundCtx := common.WithProperties(context.Background(), PropertiesFromQueryParams(ctx)) - if err := service.MyService.AppStoreManagement().RegisterAppStore(backgroundCtx, *params.Url); err != nil { + err := service.MyService.AppStoreManagement().RegisterAppStoreSync(backgroundCtx, *params.Url) + if err != nil { message := err.Error() - if err == service.ErrNotAppStore { + + switch err { + case service.ErrAppStoreSourceExists: + return ctx.JSON(http.StatusConflict, codegen.ResponseConflict{Message: &message}) + case service.ErrNotAppStore: return ctx.JSON(http.StatusBadRequest, codegen.ResponseBadRequest{Message: &message}) + default: + return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{Message: &message}) } - - return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{Message: &message}) } - logFilepath := filepath.Join(config.AppInfo.LogPath, fmt.Sprintf("%s.%s", config.AppInfo.LogSaveName, config.AppInfo.LogFileExt)) - message := fmt.Sprintf("trying to register app store asynchronously - see %s for any errors.", logFilepath) return ctx.JSON(http.StatusOK, codegen.AppStoreRegisterOK{ - Message: &message, + Message: utils.Ptr("app store is registered."), }) } diff --git a/service/appstore_management.go b/service/appstore_management.go index 5c70755d..f2584bba 100644 --- a/service/appstore_management.go +++ b/service/appstore_management.go @@ -20,13 +20,14 @@ import ( "go.uber.org/zap" ) +var ErrAppStoreSourceExists = fmt.Errorf("appstore source already exists") + type AppStoreManagement struct { + isAppUpgradable gcache.Cache + defaultAppStore AppStore + isAppUpgrading sync.Map onAppStoreRegister []func(string) error onAppStoreUnregister []func(string) error - - isAppUpgradable gcache.Cache - isAppUpgrading sync.Map - defaultAppStore AppStore } func (a *AppStoreManagement) AppStoreList() []codegen.AppStoreMetadata { @@ -99,7 +100,7 @@ func (a *AppStoreManagement) RegisterAppStore(ctx context.Context, appstoreURL s // check if appstore already exists for _, url := range config.ServerInfo.AppStoreList { if strings.EqualFold(url, appstoreURL) { - return nil + return ErrAppStoreSourceExists } } @@ -158,6 +159,66 @@ func (a *AppStoreManagement) RegisterAppStore(ctx context.Context, appstoreURL s return nil } +// TODO: refactor the function and above function +func (a *AppStoreManagement) RegisterAppStoreSync(ctx context.Context, appstoreURL string, callbacks ...func(*codegen.AppStoreMetadata)) error { + // check if appstore already exists + for _, url := range config.ServerInfo.AppStoreList { + if strings.EqualFold(url, appstoreURL) { + return ErrAppStoreSourceExists + } + } + + appstore, err := AppStoreByURL(appstoreURL) + if err != nil { + return err + } + + go PublishEventWrapper(ctx, common.EventTypeAppStoreRegisterBegin, nil) + + defer PublishEventWrapper(ctx, common.EventTypeAppStoreRegisterEnd, nil) + + defer func() { + if err == nil { + return + } + + PublishEventWrapper(ctx, common.EventTypeAppStoreRegisterError, map[string]string{ + common.PropertyTypeMessage.Name: err.Error(), + }) + }() + + if err = appstore.UpdateCatalog(); err != nil { + logger.Error("failed to update appstore catalog", zap.Error(err), zap.String("appstoreURL", appstoreURL)) + + return err + } + + // if everything is good, add to the list + config.ServerInfo.AppStoreList = append(config.ServerInfo.AppStoreList, appstoreURL) + + if err = config.SaveSetup(); err != nil { + logger.Error("failed to save appstore list", zap.Error(err), zap.String("appstoreURL", appstoreURL)) + return err + } + + for _, fn := range a.onAppStoreRegister { + if err := fn(appstoreURL); err != nil { + logger.Error("failed to run onAppStoreRegister", zap.Error(err), zap.String("appstoreURL", appstoreURL)) + } + } + + appStoreMetadata := &codegen.AppStoreMetadata{ + ID: utils.Ptr(len(config.ServerInfo.AppStoreList) - 1), + URL: &appstoreURL, + } + + for _, callback := range callbacks { + callback(appStoreMetadata) + } + + return nil +} + func (a *AppStoreManagement) UnregisterAppStore(appStoreID uint) error { if appStoreID >= uint(len(config.ServerInfo.AppStoreList)) { return fmt.Errorf("appstore id %d out of range", appStoreID)