// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package devcontainer import ( "encoding/json" "errors" "io" "net/http" "strings" "code.gitea.io/gitea/models/db" devcontainer_model "code.gitea.io/gitea/models/devcontainer" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" devcontainer_service "code.gitea.io/gitea/services/devcontainer" "code.gitea.io/gitea/services/forms" ) const ( tplRepoVariables templates.TplName = "repo/settings/devcontainer" tplOrgVariables templates.TplName = "org/settings/devcontainer" tplUserVariables templates.TplName = "user/settings/devcontainer" tplAdminVariables templates.TplName = "admin/devcontainer" ) type variablesCtx struct { OwnerID int64 RepoID int64 IsRepo bool IsOrg bool IsUser bool IsGlobal bool VariablesTemplate templates.TplName RedirectLink string } func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { if ctx.Data["PageIsRepoSettings"] == true { return &variablesCtx{ OwnerID: 0, RepoID: ctx.Repo.Repository.ID, IsRepo: true, VariablesTemplate: tplRepoVariables, RedirectLink: ctx.Repo.RepoLink + "/settings/devcontainer/variables", }, nil } if ctx.Data["PageIsOrgSettings"] == true { if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &variablesCtx{ OwnerID: ctx.ContextUser.ID, RepoID: 0, IsOrg: true, VariablesTemplate: tplOrgVariables, RedirectLink: ctx.Org.OrgLink + "/settings/devcontainer/variables", }, nil } if ctx.Data["PageIsUserSettings"] == true { return &variablesCtx{ OwnerID: ctx.Doer.ID, RepoID: 0, IsUser: true, VariablesTemplate: tplUserVariables, RedirectLink: setting.AppSubURL + "/user/settings/devcontainer/variables", }, nil } if ctx.Data["PageIsAdmin"] == true { return &variablesCtx{ OwnerID: 0, RepoID: 0, IsGlobal: true, VariablesTemplate: tplAdminVariables, RedirectLink: setting.AppSubURL + "/-/admin/devcontainer/variables", }, nil } return nil, errors.New("unable to set Variables context") } func Variables(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("devcontainer.variables") ctx.Data["PageType"] = "variables" ctx.Data["PageIsSharedSettingsDevcontainerVariables"] = true vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } variables, err := db.Find[devcontainer_model.DevcontainerVariable](ctx, devcontainer_model.FindVariablesOpts{ OwnerID: vCtx.OwnerID, RepoID: vCtx.RepoID, }) if err != nil { ctx.ServerError("FindVariables", err) return } var tags []string // 使用JOIN查询,关联DevcontainerScript表和devcontainer_variable表 err = db.GetEngine(ctx). Select("variable_name"). Table("devcontainer_script"). Where("user_id = ? AND repo_id = ?", vCtx.OwnerID, vCtx.RepoID). Find(&tags) // 将tags转换为JSON格式的字符串 tagsJSON, err := json.Marshal(tags) if err != nil { ctx.ServerError("Marshal tags", err) return } // 确保tagsJSON不为null tagsJSONStr := string(tagsJSON) if tagsJSONStr == "null" { tagsJSONStr = "[]" } // 创建一个新的请求 req, err := http.NewRequest("GET", "http://devstar.cn/variables/export", nil) if err != nil { ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{} } else { client := &http.Client{} resp, err := client.Do(req) if err != nil { ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{} } else { defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{} } else { var devstarVariables []*devcontainer_model.DevcontainerVariable err = json.Unmarshal(body, &devstarVariables) if err != nil { ctx.Data["DevstarVariables"] = []*devcontainer_model.DevcontainerVariable{} } else { // 创建一个本地变量名称的映射,用于快速查找 localVariableNames := make(map[string]bool) for _, variable := range variables { localVariableNames[variable.Name] = true } // 筛选出不与本地变量同名的devstar变量 var filteredDevstarVariables []*devcontainer_model.DevcontainerVariable for _, devstarVar := range devstarVariables { if !localVariableNames[devstarVar.Name] { filteredDevstarVariables = append(filteredDevstarVariables, devstarVar) } } ctx.Data["DevstarVariables"] = filteredDevstarVariables } } } } ctx.Data["Variables"] = variables ctx.Data["Tags"] = tagsJSONStr ctx.Data["DescriptionMaxLength"] = devcontainer_model.VariableDescriptionMaxLength ctx.HTML(http.StatusOK, vCtx.VariablesTemplate) } func GetExportVariables(ctx *context.Context) { globalVariables, err := db.Find[devcontainer_model.DevcontainerVariable](ctx, devcontainer_model.FindVariablesOpts{}) if err != nil { ctx.ServerError("Get Global Variables", err) return } // 筛选出键以"DEVSTAR_"开头的脚本 var devstarVariables []devcontainer_model.DevcontainerVariable for _, value := range globalVariables { if strings.HasPrefix(value.Name, "DEVSTAR_") { devstarVariables = append(devstarVariables, *value) } } ctx.JSON(http.StatusOK, devstarVariables) } func VariableCreate(ctx *context.Context) { vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } if ctx.HasError() { // form binding validation error ctx.JSONError(ctx.GetErrMsg()) return } form := web.GetForm(ctx).(*forms.EditVariableForm) v, err := devcontainer_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data, form.Description) if err != nil { log.Error("CreateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) ctx.JSONRedirect(vCtx.RedirectLink) } func ScriptCreate(ctx *context.Context) { vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } if ctx.HasError() { // form binding validation error ctx.JSONError(ctx.GetErrMsg()) return } query := ctx.Req.URL.Query() var script *devcontainer_model.DevcontainerScript // 首先检查变量是否在DevcontainerVariable表中存在 exists, err := db.GetEngine(ctx). Table("devcontainer_variable"). Where("(owner_id = 0 AND repo_id = 0) OR (owner_id = ? AND repo_id = 0) OR (owner_id = 0 AND repo_id = ?)", vCtx.OwnerID, vCtx.RepoID). And("name = ?", strings.ToUpper(query.Get("name"))). Exist() if err != nil { log.Error("Check variable existence: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } if !exists { // 创建一个新的请求来获取devstar变量 req, err := http.NewRequest("GET", "http://devstar.cn/variables/export", nil) if err != nil { log.Error("Failed to create request for devstar variables: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Error("Failed to fetch devstar variables: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Error("Failed to read devstar variables response: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } var devstarVariables []*devcontainer_model.DevcontainerVariable err = json.Unmarshal(body, &devstarVariables) if err != nil { log.Error("Failed to unmarshal devstar variables: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } // 查找是否有匹配的devstar变量 foundInDevstar := false searchName := strings.ToUpper(query.Get("name")) for _, devstarVar := range devstarVariables { if devstarVar.Name == searchName { foundInDevstar = true break } } if !foundInDevstar { log.Error("Variable %s does not exist", query.Get("name")) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } } // 创建devcontainer_script记录 script = &devcontainer_model.DevcontainerScript{ UserId: vCtx.OwnerID, RepoId: vCtx.RepoID, VariableName: strings.ToUpper(query.Get("name")), } _, err = db.GetEngine(ctx).Insert(script) if err != nil { log.Error("CreateScript: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } } func VariableUpdate(ctx *context.Context) { vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } if ctx.HasError() { // form binding validation error ctx.JSONError(ctx.GetErrMsg()) return } id := ctx.PathParamInt64("variable_id") variable := findActionsVariable(ctx, id, vCtx) if ctx.Written() { return } form := web.GetForm(ctx).(*forms.EditVariableForm) variable.Name = form.Name variable.Data = form.Data variable.Description = form.Description if ok, err := devcontainer_service.UpdateVariableNameData(ctx, variable); err != nil || !ok { log.Error("UpdateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.update.failed")) return } ctx.Flash.Success(ctx.Tr("actions.variables.update.success")) ctx.JSONRedirect(vCtx.RedirectLink) } func findActionsVariable(ctx *context.Context, id int64, vCtx *variablesCtx) *devcontainer_model.DevcontainerVariable { opts := devcontainer_model.FindVariablesOpts{ IDs: []int64{id}, } switch { case vCtx.IsRepo: opts.RepoID = vCtx.RepoID if opts.RepoID == 0 { panic("RepoID is 0") } case vCtx.IsOrg, vCtx.IsUser: opts.OwnerID = vCtx.OwnerID if opts.OwnerID == 0 { panic("OwnerID is 0") } case vCtx.IsGlobal: // do nothing default: panic("invalid actions variable") } got, err := devcontainer_model.FindVariables(ctx, opts) if err != nil { ctx.ServerError("FindVariables", err) return nil } else if len(got) == 0 { ctx.NotFound(nil) return nil } return got[0] } func VariableDelete(ctx *context.Context) { vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } id := ctx.PathParamInt64("variable_id") variable := findActionsVariable(ctx, id, vCtx) if ctx.Written() { return } if err := devcontainer_service.DeleteVariableByID(ctx, variable.ID); err != nil { log.Error("Delete variable [%d] failed: %v", id, err) ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) return } // 删除相应的script记录,根据repoId、userId和name script := &devcontainer_model.DevcontainerScript{ UserId: vCtx.OwnerID, RepoId: vCtx.RepoID, VariableName: variable.Name, } _, err = db.GetEngine(ctx).Delete(script) if err != nil { log.Error("Delete script for variable [%d] failed: %v", id, err) // 注意:这里我们记录错误但不中断变量删除过程 } ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) ctx.JSONRedirect(vCtx.RedirectLink) } func ScriptDelete(ctx *context.Context) { vCtx, err := getVariablesCtx(ctx) if err != nil { ctx.ServerError("getVariablesCtx", err) return } if ctx.HasError() { // form binding validation error ctx.JSONError(ctx.GetErrMsg()) return } query := ctx.Req.URL.Query() // 删除devcontainer_script记录 script := &devcontainer_model.DevcontainerScript{ UserId: vCtx.OwnerID, RepoId: vCtx.RepoID, VariableName: query.Get("name"), } _, err = db.GetEngine(ctx).Delete(script) if err != nil { log.Error("DeleteScript: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } }