Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

98 lines
2.7 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
// Package appmeta exposes read-only views of a Feishu app's published version, subscribed event types, and scopes.
package appmeta
import (
"context"
"encoding/json"
"fmt"
"github.com/larksuite/cli/internal/event"
)
// APIClient aliases event.APIClient so one concrete adapter satisfies event, appmeta, and consume.
type APIClient = event.APIClient
// AppVersion is the projected subset of one /app_versions item preflight cares about.
type AppVersion struct {
VersionID string
Version string
EventTypes []string
TenantScopes []string
}
const appVersionStatusPublished = 1
// FetchCurrentPublished returns the most recently published version of appID, or (nil, nil) if never published.
// page_size=2 suffices: Feishu disallows a new version while an in-progress one exists, so the first status==1 item with publish_time is the live one.
func FetchCurrentPublished(ctx context.Context, client APIClient, appID string) (*AppVersion, error) {
path := fmt.Sprintf(
"/open-apis/application/v6/applications/%s/app_versions?lang=zh_cn&page_size=2",
appID,
)
raw, err := client.CallAPI(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var envelope struct {
Data struct {
Items []struct {
VersionID string `json:"version_id"`
Version string `json:"version"`
Status int `json:"status"`
PublishTime json.RawMessage `json:"publish_time"`
EventInfos []struct {
EventType string `json:"event_type"`
} `json:"event_infos"`
Scopes []struct {
Scope string `json:"scope"`
TokenTypes []string `json:"token_types"`
} `json:"scopes"`
} `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(raw, &envelope); err != nil {
return nil, fmt.Errorf("decode app_versions response: %w", err)
}
for _, it := range envelope.Data.Items {
if it.Status != appVersionStatusPublished || !publishTimeSet(it.PublishTime) {
continue
}
v := &AppVersion{
VersionID: it.VersionID,
Version: it.Version,
}
for _, e := range it.EventInfos {
if e.EventType != "" {
v.EventTypes = append(v.EventTypes, e.EventType)
}
}
for _, s := range it.Scopes {
if s.Scope != "" && containsString(s.TokenTypes, "tenant") {
v.TenantScopes = append(v.TenantScopes, s.Scope)
}
}
return v, nil
}
return nil, nil
}
// publishTimeSet rejects null and empty-string; any other value is a real publish_time.
func publishTimeSet(raw json.RawMessage) bool {
s := string(raw)
return s != "" && s != "null" && s != `""`
}
func containsString(haystack []string, needle string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}