/
pyo
/
updater
Обзор
Документация
Войти
/
pyo
/
updater
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
develop
pkg/components/server.go
670 строк
23 KB
ilovepitsa
restore from another repo
16 июн 2025, 18:36
16 июн 2025, 18:36
91b9a34
Код
Авторство
О чём код?
package components import ( "context" "encoding/json" "net/http" "os" "path/filepath" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/spf13/afero" "github.com/volatiletech/null" "gitverse.ru/pyo/updater/pkg/config" "gitverse.ru/pyo/updater/pkg/middleware" "gitverse.ru/pyo/updater/pkg/models" "gitverse.ru/pyo/updater/pkg/server" "gitverse.ru/pyo/updater/pkg/util" "go.uber.org/zap" ) const ( EchoCtxKeyBinds = "updater_service_binds" ) type ComponentsRepo interface { GetComponents(ctx context.Context) ([]models.DBComponent, error) GetNewerComponentsVersion(ctx context.Context, current []models.DBComponentWithVersion) ([]models.DBComponentWithVersion, error) GetComponentVersions(ctx context.Context, name string) ([]models.DBVersion, error) GetComponentByName(ctx context.Context, name string) (models.DBComponent, error) GetComponentWithVersionByNameAndVersion(ctx context.Context, name, version string) (models.DBComponentWithVersion, error) AddComponent(ctx context.Context, comp models.DBComponent) (int64, error) AddComponentVersion(ctx context.Context, compName string, version models.DBVersion) (int64, error) AddBuildOperation(ctx context.Context, op models.BuildOperation) (int, error) AddComponentVersionByName(ctx context.Context, c models.Component, name string) error UpdateComponent(ctx context.Context, comp models.DBComponent) error UpdateVersion(ctx context.Context, compName string, version models.DBVersion) error } type ComponentsServer struct { Pool *pgxpool.Pool Cfg *config.Config Repo ComponentsRepo Logger *zap.Logger Fs afero.Afero } func (cs *ComponentsServer) getComponentsWithVersios(ctx context.Context) ([]models.ComponentWithVersions, error) { dbComponents, err := cs.Repo.GetComponents(ctx) if err != nil { return nil, err } components := models.MapDBComponentsToComponents(dbComponents) componentsWithVersions := make([]models.ComponentWithVersions, 0, len(components)) for _, comp := range components { dbVersions, err := cs.Repo.GetComponentVersions(ctx, comp.Name) if err != nil { return nil, err } versions := models.MapDBVersionsToVersions(dbVersions) componentsWithVersions = append(componentsWithVersions, models.ComponentWithVersions{ Component: comp, Versions: versions, }) } return componentsWithVersions, nil } // Получение списка все компонент в системе // (GET /components/) func (cs *ComponentsServer) GetComponents(ctx echo.Context) error { components, err := cs.Repo.GetComponents(ctx.Request().Context()) if err != nil { cs.Logger.Error("Cant get components", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } resp, err := cs.fillGetComponentsRespose(ctx.Request().Context(), models.MapDBComponentsToComponents(components)) if err != nil { cs.Logger.Error("cant fill response", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } return ctx.JSON(http.StatusOK, resp) } func (cs *ComponentsServer) fillGetComponentsRespose(ctx context.Context, comp []models.Component) (server.GetComponentsResponse, error) { res := server.GetComponentsResponse{} for _, c := range comp { versions, err := cs.Repo.GetComponentVersions(ctx, c.Name) if err != nil { return nil, err } res = append(res, server.ComponentDescriptionWithVersions{ Description: util.Ptr(models.MapComponentToServerComponentDescription(c)), Version: util.Ptr(models.MapVersionsToServerVersions(models.MapDBVersionsToVersions(versions))), }) } return res, nil } // Проверка наличия обновлений // (POST /components/check-updates) func (cs *ComponentsServer) PostComponentsCheckUpdates(ctx echo.Context) error { params, ok := middleware.EchoCtxGet[server.EchoPostComponentsCheckUpdatesParams](ctx, EchoCtxKeyBinds) if !ok || params.Body == nil { cs.Logger.Error("Invalid input") return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("invalid input"). Handle(ctx) } comp, err := models.MapUpdateElementsToComponent(params.Body) if err != nil { return server.ErrorBuilder(). WithMessage("invalid version"). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } dbComp := models.MapComponentsWithVersionToDBComponentsWithVersion(comp) versions, err := cs.Repo.GetNewerComponentsVersion(ctx.Request().Context(), dbComp) if err != nil && !util.IsNotFound(err) { cs.Logger.Error("cant get new versions from db") return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get new versions from db"). FromErr(err). Handle(ctx) } resp := models.MapComponentsToUpdateElements(models.MapDBComponentsWithVersionToComponentsWithVersion(versions)) return ctx.JSON(http.StatusOK, resp) } // Получение информации о конкретной компоненте // (GET /components/{name}) func (cs *ComponentsServer) GetComponentsName(ctx echo.Context, name string) error { componentInfo, err := cs.Repo.GetComponentByName(ctx.Request().Context(), name) if err != nil { cs.Logger.Error("cant get component info by name", zap.String("name", name), zap.Error(err)) return server.ErrorBuilder(). WithMessage("cant get component info by name"). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } versions, err := cs.Repo.GetComponentVersions(ctx.Request().Context(), name) if err != nil { cs.Logger.Error("cant get component versions", zap.String("name", name), zap.Error(err)) return server.ErrorBuilder(). WithMessage("cant get component versions"). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } comp := models.MapComponentWithVersionsToComponentDescriptionWithVersions(models.MapDBComponentWithVersionsToComponentWithVersions(models.DBComponentWithVersions{ DBComponent: componentInfo, Versions: versions, })) return ctx.JSON(http.StatusOK, comp) } // Получение информации о конкретной компоненте // (GET /components/{name}/{version}) func (cs *ComponentsServer) GetComponentsNameVersion(ctx echo.Context, name string, version string) error { component, err := cs.Repo.GetComponentWithVersionByNameAndVersion(ctx.Request().Context(), name, version) if err != nil { cs.Logger.Error("cant get component version", zap.String("name", name), zap.Error(err)) return server.ErrorBuilder(). WithMessage("cant get component version"). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } resp := models.MapComponentWithVersionToComponentDescriptionWithVersion(models.MapDBComponentWithVersionToComponentWithVersion(component)) return ctx.JSON(http.StatusOK, resp) } // Загрузка компонента системы моделирования // (POST /publish) func (cs *ComponentsServer) PublishComponent(ctx echo.Context) error { contx := ctx.Request().Context() params, ok := middleware.EchoCtxGet[server.EchoPublishComponentParams](ctx, EchoCtxKeyBinds) if !ok { cs.Logger.Error("Invalid input", zap.Bool("mapped", ok), zap.Any("description", params.Body.Description), zap.Any("file", params.Body.File)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } desc := server.ComponentDescriptionWithVersion{} err := json.Unmarshal([]byte(params.Body.Description), &desc) if err != nil { cs.Logger.Error("cant parse component settings", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } comp, err := models.MapServerComponentToComponent(desc) if err != nil { cs.Logger.Error("cant map component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } buff, err := params.Body.File.Bytes() if err != nil { cs.Logger.Error("cant get components files", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get components files"). FromErr(err). Handle(ctx) } compPath := util.CreateComponentPath(cs.Cfg.TempDir, comp.Name) //TODO: add rollback on this err = cs.Fs.WriteFile(compPath, buff, os.FileMode(cs.Cfg.FileMode)) if err != nil { cs.Logger.Error("cant write file to fs", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant write file to fs"). FromErr(err). Handle(ctx) } tx, err := cs.Pool.BeginTx(contx, pgx.TxOptions{ //TODO }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant create transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant create transaction"). FromErr(err). Handle(ctx) } defer func() { _ = tx.Rollback(contx) }() contx = util.TxToContext(contx, tx) compID, err := cs.Repo.AddComponent(contx, models.MapComponentToDBComponent(comp.Component)) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant save component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error save component"). Handle(ctx) } comp.ID = compID _, err = cs.Repo.AddComponentVersion(contx, comp.Name, models.MapVersionToDBVersion(comp.Version)) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant save component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error add component version"). Handle(ctx) } now := time.Now() _, err = cs.Repo.AddBuildOperation(contx, models.BuildOperation{ CreatedAt: null.TimeFrom(now), UpdatedAt: null.TimeFrom(now), ComponentPath: compPath, ComponentID: compID, Version: comp.Version.Version.String(), Status: models.BuildStatusCreated, OperationType: models.BuildTypeBuild, }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("add op to db", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } err = tx.Commit(contx) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("commit transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant commit transaction"). FromErr(err). Handle(ctx) } return ctx.JSON(http.StatusOK, nil) } // Загрузка новой версии компонента системы моделирования // (POST /publish/{name}) func (cs *ComponentsServer) PublishComponentVersion(ctx echo.Context, name string) error { contx := ctx.Request().Context() params, ok := middleware.EchoCtxGet[server.EchoPublishComponentVersionParams](ctx, EchoCtxKeyBinds) if !ok { cs.Logger.Error("Invalid input", zap.Bool("mapped", ok), zap.Any("description", params.Body.Description), zap.Any("file", params.Body.File)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } desc := server.Version{} err := json.Unmarshal([]byte(params.Body.Description), &desc) if err != nil { cs.Logger.Error("cant map component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } vers, err := models.MapServerVersionToVersion(desc) if err != nil { cs.Logger.Error("cant map component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } buff, err := params.Body.File.Bytes() if err != nil { cs.Logger.Error("cant get components files", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get components files"). FromErr(err). Handle(ctx) } compPath := util.CreateComponentPath(cs.Cfg.TempDir, name) err = cs.Fs.WriteFile(compPath, buff, os.FileMode(cs.Cfg.FileMode)) if err != nil { cs.Logger.Error("cant write file to fs", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant write file to fs"). FromErr(err). Handle(ctx) } tx, err := cs.Pool.BeginTx(contx, pgx.TxOptions{ //TODO }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant create transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant create transaction"). FromErr(err). Handle(ctx) } defer func() { _ = tx.Rollback(contx) }() contx = util.TxToContext(contx, tx) _, err = cs.Repo.AddComponentVersion(contx, name, models.MapVersionToDBVersion(vers)) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant save component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error add component version"). Handle(ctx) } comp, err := cs.Repo.GetComponentByName(contx, name) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant get component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error get component"). Handle(ctx) } now := time.Now() _, err = cs.Repo.AddBuildOperation(contx, models.BuildOperation{ CreatedAt: null.TimeFrom(now), UpdatedAt: null.TimeFrom(now), ComponentPath: compPath, ComponentID: comp.ID, Version: vers.Version.String(), Status: models.BuildStatusCreated, OperationType: models.BuildTypeBuild, }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("add op to db", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } err = tx.Commit(contx) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("commit transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant commit transaction"). FromErr(err). Handle(ctx) } return ctx.JSON(http.StatusOK, nil) } // Обновление компоненты // (PUT /publish/{name}) func (cs *ComponentsServer) PutPublishName(ctx echo.Context, name string) error { contx := ctx.Request().Context() params, ok := middleware.EchoCtxGet[server.EchoPutPublishNameParams](ctx, EchoCtxKeyBinds) if !ok { cs.Logger.Error("Invalid input") return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } comp, err := models.MapServerComponentDesciptionToComponent(params.Body.Description) if err != nil { cs.Logger.Error("cant map component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } tx, err := cs.Pool.BeginTx(contx, pgx.TxOptions{ //TODO }) if err != nil { cs.Logger.Error("cant create transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant create transaction"). FromErr(err). Handle(ctx) } defer func() { _ = tx.Rollback(contx) }() contx = util.TxToContext(contx, tx) err = cs.Repo.UpdateComponent(contx, models.MapComponentToDBComponent(comp)) if err != nil { cs.Logger.Error("cant update component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error update component"). Handle(ctx) } return ctx.JSON(http.StatusOK, nil) } // Обновление компоненты // (PUT /publish/{name}/{version}) func (cs *ComponentsServer) PutPublishNameVersion(ctx echo.Context, name string, version string) error { contx := ctx.Request().Context() params, ok := middleware.EchoCtxGet[server.EchoPutPublishNameVersionParams](ctx, EchoCtxKeyBinds) if !ok { cs.Logger.Error("Invalid input") return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } desc := server.Version{} if err := json.Unmarshal([]byte(params.Body.Description), &desc); err != nil { cs.Logger.Error("cant parse component settings", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } vers, err := models.MapServerVersionToVersion(desc) if err != nil { cs.Logger.Error("cant map component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("cant parse component settings"). FromErr(err). Handle(ctx) } buff, err := params.Body.File.Bytes() if err != nil { cs.Logger.Error("cant get components files", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get components files"). FromErr(err). Handle(ctx) } compPath := util.CreateComponentPath(cs.Cfg.TempDir, name) err = cs.Fs.WriteFile(compPath, buff, os.FileMode(cs.Cfg.FileMode)) if err != nil { cs.Logger.Error("cant save components files", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant save components files"). FromErr(err). Handle(ctx) } tx, err := cs.Pool.BeginTx(contx, pgx.TxOptions{ //TODO }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant create transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant create transaction"). FromErr(err). Handle(ctx) } defer func() { _ = tx.Rollback(contx) }() contx = util.TxToContext(contx, tx) err = cs.Repo.UpdateVersion(contx, name, models.MapVersionToDBVersion(vers)) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant update version", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant update version"). Handle(ctx) } comp, err := cs.Repo.GetComponentByName(contx, name) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("cant get component", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("error get component"). Handle(ctx) } now := time.Now() _, err = cs.Repo.AddBuildOperation(contx, models.BuildOperation{ CreatedAt: null.TimeFrom(now), UpdatedAt: null.TimeFrom(now), ComponentPath: compPath, ComponentID: comp.ID, Version: vers.Version.String(), Status: models.BuildStatusCreated, OperationType: models.BuildTypeBuild, }) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("add op to db", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } err = tx.Commit(contx) if err != nil { cs.Fs.Remove(compPath) cs.Logger.Error("commit transaction", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant commit transaction"). FromErr(err). Handle(ctx) } return ctx.JSON(http.StatusOK, nil) } // Получение последней версии компоненты // (GET /update/{name}/{version}) func (cs *ComponentsServer) GetUpdateNameVersion(ctx echo.Context, name string, version string, params server.GetUpdateNameVersionParams) error { if name == "" || version == "" || params.Platform == "" { cs.Logger.Error("ivalid input", zap.String("name", name), zap.String("version", version)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } comp, err := cs.Repo.GetComponentWithVersionByNameAndVersion(ctx.Request().Context(), name, version) if err != nil { cs.Logger.Error("cant get comp from db", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get comp from db"). FromErr(err). Handle(ctx) } info, err := cs.Fs.ReadFile(filepath.Join(cs.Cfg.Dir, comp.Name, comp.Version, comp.Name) + "-" + params.Platform + cs.Cfg.Ext) if err != nil || info == nil { cs.Logger.Error("cant read file", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). WithMessage("cant read file"). Handle(ctx) } // var resp openapi_types.File // resp.InitFromBytes(info, comp.Name+"-"+comp.Version+"-"+params.Platform+".zip") return ctx.Blob(http.StatusOK, "", info) } // Получение доступных версий конкретной компоненты // (GET /components/{name}/versions) func (cs *ComponentsServer) GetComponentsNameVersions(ctx echo.Context, name string) error { if name == "" { cs.Logger.Error("invalid input") return server.ErrorBuilder(). WithCode(http.StatusBadRequest). Handle(ctx) } versions, err := cs.Repo.GetComponentVersions(ctx.Request().Context(), name) if err != nil { cs.Logger.Error("cant get versions") return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). Handle(ctx) } resp := models.MapVersionsToServerVersions(models.MapDBVersionsToVersions(versions)) return ctx.JSON(http.StatusOK, resp) } // Получение определенной версии компоненты // (GET /update/{name}/{version}/info) func (cs *ComponentsServer) GetUpdateNameVersionInfo(ctx echo.Context, name string, version string, params server.GetUpdateNameVersionInfoParams) error { if name == "" || version == "" || params.Platform == "" { cs.Logger.Error("ivalid input", zap.String("name", name), zap.String("version", version)) return server.ErrorBuilder(). WithCode(http.StatusBadRequest). WithMessage("invalid input"). Handle(ctx) } dbComp, err := cs.Repo.GetComponentWithVersionByNameAndVersion(ctx.Request().Context(), name, version) if err != nil { cs.Logger.Error("cant get comp from db", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). WithMessage("cant get comp from db"). FromErr(err). Handle(ctx) } comp := models.MapDBComponentWithVersionToComponentWithVersion(dbComp) info, err := cs.Fs.ReadFile(filepath.Join(cs.Cfg.Dir, comp.Name, comp.Version.Version.String(), comp.Name) + "-" + params.Platform + ".sha256") if err != nil || info == nil { cs.Logger.Error("cant read file", zap.Error(err)) return server.ErrorBuilder(). WithCode(http.StatusInternalServerError). FromErr(err). WithMessage("cant read file"). Handle(ctx) } var resp server.ComponentDownloadInfo resp.Sha = util.Ptr(string(info)) resp.Type = util.Ptr(string(models.MapComponentTypeToServerComponentType(comp.ComponentType))) return ctx.JSON(http.StatusOK, resp) }