mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 16:18:05 +08:00
271 lines
9.7 KiB
Go
271 lines
9.7 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package base
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
"github.com/larksuite/cli/shortcuts/common"
|
|
)
|
|
|
|
const baseCreateHint = "Tip: Base created the platform default first-table schema. To configure the initial table schema during +base-create, pass both --table-name and --fields."
|
|
|
|
var baseCreateDefaultTableDeleteDelay = time.Second
|
|
|
|
func dryRunBaseGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
|
return common.NewDryRunAPI().
|
|
GET("/open-apis/base/v3/bases/:base_token").
|
|
Set("base_token", runtime.Str("base-token"))
|
|
}
|
|
|
|
func dryRunBaseCopy(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
|
d := common.NewDryRunAPI().
|
|
POST("/open-apis/base/v3/bases/:base_token/copy").
|
|
Body(buildBaseCopyBody(runtime)).
|
|
Set("base_token", runtime.Str("base-token"))
|
|
if runtime.IsBot() {
|
|
d.Desc("After Base copy succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new Base.")
|
|
}
|
|
return d
|
|
}
|
|
|
|
func dryRunBaseCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
|
d := common.NewDryRunAPI()
|
|
if runtime.IsBot() {
|
|
d.Desc("After Base creation succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new Base.")
|
|
}
|
|
d.
|
|
POST("/open-apis/base/v3/bases").
|
|
Body(buildBaseCreateBody(runtime))
|
|
hasFields := strings.TrimSpace(runtime.Str("fields")) != ""
|
|
hasTableName := strings.TrimSpace(runtime.Str("table-name")) != ""
|
|
if hasFields {
|
|
d.GET("/open-apis/base/v3/bases/:created_base_token/tables").
|
|
Params(map[string]interface{}{"offset": 0, "limit": 100}).
|
|
Desc("If the create response does not include the default table ID, the CLI lists tables to find it.").
|
|
Set("created_base_token", "<created_base_token>")
|
|
d.POST("/open-apis/base/v3/bases/:created_base_token/tables").
|
|
Body(dryRunTableCreateBody(runtime, baseCreateFirstTableName(runtime))).
|
|
Desc("Create a replacement first table with --fields in the create-table body.")
|
|
d.DELETE("/open-apis/base/v3/bases/:created_base_token/tables/:default_table_id").
|
|
Desc("After a 1s wait, delete the default table created with the Base.").
|
|
Set("default_table_id", "<default_table_id>")
|
|
} else if hasTableName {
|
|
d.GET("/open-apis/base/v3/bases/:created_base_token/tables").
|
|
Params(map[string]interface{}{"offset": 0, "limit": 100}).
|
|
Desc("If the create response does not include the default table ID, the CLI lists tables to find it.").
|
|
Set("created_base_token", "<created_base_token>")
|
|
d.PATCH("/open-apis/base/v3/bases/:created_base_token/tables/:default_table_id").
|
|
Body(map[string]interface{}{"name": baseCreateFirstTableName(runtime)}).
|
|
Desc("Rename the default first table when only --table-name is provided.").
|
|
Set("default_table_id", "<default_table_id>")
|
|
}
|
|
return d
|
|
}
|
|
|
|
func validateBaseCreate(runtime *common.RuntimeContext) error {
|
|
return nil
|
|
}
|
|
|
|
func executeBaseGet(runtime *common.RuntimeContext) error {
|
|
data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token")), nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
runtime.Out(map[string]interface{}{"base": data}, nil)
|
|
return nil
|
|
}
|
|
|
|
func executeBaseCopy(runtime *common.RuntimeContext) error {
|
|
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "copy"), nil, buildBaseCopyBody(runtime))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out := map[string]interface{}{"base": data, "copied": true}
|
|
augmentBasePermissionGrant(runtime, out, data)
|
|
runtime.Out(out, nil)
|
|
return nil
|
|
}
|
|
|
|
func executeBaseCreate(runtime *common.RuntimeContext) error {
|
|
data, err := baseV3Call(runtime, "POST", baseV3Path("bases"), nil, buildBaseCreateBody(runtime))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out := map[string]interface{}{"base": data, "created": true}
|
|
if strings.TrimSpace(runtime.Str("fields")) != "" {
|
|
customTable, createdFields, defaultTableID, err := replaceBaseDefaultTable(runtime, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out["table"] = customTable
|
|
out["fields"] = createdFields
|
|
out["default_table_deleted"] = true
|
|
out["deleted_default_table_id"] = defaultTableID
|
|
} else if strings.TrimSpace(runtime.Str("table-name")) != "" {
|
|
renamedTable, defaultTableID, err := renameBaseDefaultTable(runtime, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out["table"] = renamedTable
|
|
out["default_table_renamed"] = true
|
|
out["renamed_default_table_id"] = defaultTableID
|
|
} else {
|
|
fmt.Fprintln(runtime.IO().ErrOut, baseCreateHint)
|
|
}
|
|
augmentBasePermissionGrant(runtime, out, data)
|
|
runtime.Out(out, nil)
|
|
return nil
|
|
}
|
|
|
|
func buildBaseCopyBody(runtime *common.RuntimeContext) map[string]interface{} {
|
|
body := map[string]interface{}{}
|
|
if name := strings.TrimSpace(runtime.Str("name")); name != "" {
|
|
body["name"] = name
|
|
}
|
|
if folderToken := strings.TrimSpace(runtime.Str("folder-token")); folderToken != "" {
|
|
body["folder_token"] = folderToken
|
|
}
|
|
if runtime.Bool("without-content") {
|
|
body["without_content"] = true
|
|
}
|
|
if timeZone := strings.TrimSpace(runtime.Str("time-zone")); timeZone != "" {
|
|
body["time_zone"] = timeZone
|
|
}
|
|
return body
|
|
}
|
|
|
|
func buildBaseCreateBody(runtime *common.RuntimeContext) map[string]interface{} {
|
|
body := map[string]interface{}{"name": runtime.Str("name")}
|
|
if folderToken := strings.TrimSpace(runtime.Str("folder-token")); folderToken != "" {
|
|
body["folder_token"] = folderToken
|
|
}
|
|
if timeZone := strings.TrimSpace(runtime.Str("time-zone")); timeZone != "" {
|
|
body["time_zone"] = timeZone
|
|
}
|
|
return body
|
|
}
|
|
|
|
func augmentBasePermissionGrant(runtime *common.RuntimeContext, out, base map[string]interface{}) {
|
|
if grant := common.AutoGrantCurrentUserDrivePermission(runtime, extractBasePermissionToken(base), "bitable"); grant != nil {
|
|
out["permission_grant"] = grant
|
|
}
|
|
}
|
|
|
|
func extractBasePermissionToken(base map[string]interface{}) string {
|
|
for _, key := range []string{"base_token", "app_token"} {
|
|
if token := strings.TrimSpace(common.GetString(base, key)); token != "" {
|
|
return token
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func replaceBaseDefaultTable(runtime *common.RuntimeContext, base map[string]interface{}) (map[string]interface{}, []interface{}, string, error) {
|
|
baseToken := extractBasePermissionToken(base)
|
|
if baseToken == "" {
|
|
return nil, nil, "", errs.NewInternalError(errs.SubtypeInvalidResponse, "+base-create --fields could not find base_token/app_token in create response")
|
|
}
|
|
defaultTableID, err := findCreatedBaseDefaultTableID(runtime, baseToken, base, "--fields")
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
tableBody, err := buildTableCreateBody(runtime, newParseCtx(runtime), baseCreateFirstTableName(runtime))
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
customTable, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseToken, "tables"), nil, tableBody)
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
customTableID := tableID(customTable)
|
|
if customTableID == "" {
|
|
return nil, nil, "", errs.NewInternalError(errs.SubtypeInvalidResponse, "+base-create --fields could not find table_id/id in replacement table create response")
|
|
}
|
|
createdFields := []interface{}{}
|
|
if fields, ok := customTable["fields"].([]interface{}); ok {
|
|
createdFields = fields
|
|
}
|
|
time.Sleep(baseCreateDefaultTableDeleteDelay)
|
|
if _, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", baseToken, "tables", defaultTableID), nil, nil); err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
return customTable, createdFields, defaultTableID, nil
|
|
}
|
|
|
|
func renameBaseDefaultTable(runtime *common.RuntimeContext, base map[string]interface{}) (map[string]interface{}, string, error) {
|
|
baseToken := extractBasePermissionToken(base)
|
|
if baseToken == "" {
|
|
return nil, "", errs.NewInternalError(errs.SubtypeInvalidResponse, "+base-create --table-name could not find base_token/app_token in create response")
|
|
}
|
|
defaultTableID, err := findCreatedBaseDefaultTableID(runtime, baseToken, base, "--table-name")
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
renamedTable, err := baseV3Call(
|
|
runtime,
|
|
"PATCH",
|
|
baseV3Path("bases", baseToken, "tables", defaultTableID),
|
|
nil,
|
|
map[string]interface{}{"name": baseCreateFirstTableName(runtime)},
|
|
)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return renamedTable, defaultTableID, nil
|
|
}
|
|
|
|
func baseCreateFirstTableName(runtime *common.RuntimeContext) string {
|
|
if name := strings.TrimSpace(runtime.Str("table-name")); name != "" {
|
|
return name
|
|
}
|
|
return "Table 1"
|
|
}
|
|
|
|
func findCreatedBaseDefaultTableID(runtime *common.RuntimeContext, baseToken string, base map[string]interface{}, flag string) (string, error) {
|
|
if tableIDValue := tableIDFromCreateBaseResponse(base); tableIDValue != "" {
|
|
return tableIDValue, nil
|
|
}
|
|
tables, _, err := listAllTables(runtime, baseToken, 0, 100)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(tables) == 0 {
|
|
return "", errs.NewInternalError(errs.SubtypeInvalidResponse, "+base-create "+flag+" could not find the default table created with the Base")
|
|
}
|
|
if id := tableID(tables[0]); id != "" {
|
|
return id, nil
|
|
}
|
|
return "", errs.NewInternalError(errs.SubtypeInvalidResponse, "+base-create "+flag+" could not find table_id/id in default table list response")
|
|
}
|
|
|
|
func tableIDFromCreateBaseResponse(base map[string]interface{}) string {
|
|
for _, key := range []string{"table_id", "default_table_id"} {
|
|
if id := strings.TrimSpace(common.GetString(base, key)); id != "" {
|
|
return id
|
|
}
|
|
}
|
|
for _, key := range []string{"table", "default_table"} {
|
|
if table, ok := base[key].(map[string]interface{}); ok {
|
|
if id := tableID(table); id != "" {
|
|
return id
|
|
}
|
|
}
|
|
}
|
|
for _, key := range []string{"tables", "default_tables"} {
|
|
if tables, ok := base[key].([]interface{}); ok && len(tables) > 0 {
|
|
if table, ok := tables[0].(map[string]interface{}); ok {
|
|
if id := tableID(table); id != "" {
|
|
return id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|