mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
c85373ff47
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":43}
179 lines
5.4 KiB
Go
179 lines
5.4 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package option
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/geoip"
|
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/uptimekuma"
|
|
"github.com/Rain-kl/Wavelet/internal/buildinfo"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
|
)
|
|
|
|
type publicAuthSourceView struct {
|
|
ID uint64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
DisplayName string `json:"display_name"`
|
|
AuthorizeURL string `json:"authorize_url"`
|
|
IconURL string `json:"icon_url"`
|
|
}
|
|
|
|
type statusView struct {
|
|
Version string `json:"version"`
|
|
StartTime int64 `json:"start_time"`
|
|
EmailVerification bool `json:"email_verification"`
|
|
ServerAddress string `json:"server_address"`
|
|
PasswordRegisterEnabled bool `json:"password_register_enabled"`
|
|
CapLoginEnabled bool `json:"cap_login_enabled"`
|
|
AuthSources []publicAuthSourceView `json:"auth_sources"`
|
|
}
|
|
|
|
type geoIPLookupRequest struct {
|
|
Provider string `json:"provider"`
|
|
IP string `json:"ip"`
|
|
}
|
|
|
|
type geoIPLookupView struct {
|
|
Provider string `json:"provider"`
|
|
IP string `json:"ip"`
|
|
ISOCode string `json:"iso_code"`
|
|
Name string `json:"name"`
|
|
Latitude *float64 `json:"latitude,omitempty"`
|
|
Longitude *float64 `json:"longitude,omitempty"`
|
|
}
|
|
|
|
type optionBatchPayload struct {
|
|
Options []model.OpenFlareOption `json:"options"`
|
|
}
|
|
|
|
func listOptions(ctx context.Context) ([]model.OpenFlareOption, error) {
|
|
// 从 SystemConfig 读取所有业务配置
|
|
configs, err := repository.ListAdminSystemConfigs(ctx, "business")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
options := make([]model.OpenFlareOption, 0, len(configs))
|
|
for _, config := range configs {
|
|
// 跳过敏感配置(如密码、令牌)
|
|
if config.Visibility == model.ConfigVisibilityHidden && isSecretConfigKey(config.Key) {
|
|
continue
|
|
}
|
|
// 将 snake_case key 转换为 PascalCase 以保持向后兼容
|
|
options = append(options, model.OpenFlareOption{
|
|
Key: config.Key,
|
|
Value: config.Value,
|
|
})
|
|
}
|
|
return options, nil
|
|
}
|
|
|
|
func updateOption(ctx context.Context, option model.OpenFlareOption) error {
|
|
return updateOptions(ctx, []model.OpenFlareOption{option})
|
|
}
|
|
|
|
func updateOptionsBatch(ctx context.Context, payload optionBatchPayload) error {
|
|
if len(payload.Options) == 0 {
|
|
return errors.New(errInvalidParams)
|
|
}
|
|
return updateOptions(ctx, payload.Options)
|
|
}
|
|
|
|
func updateOptions(ctx context.Context, options []model.OpenFlareOption) error {
|
|
if err := validateOptions(ctx, options); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 将每个 option 更新到 SystemConfig
|
|
for _, opt := range options {
|
|
if err := repository.SaveOrUpdateSystemConfig(ctx, opt.Key, opt.Value); err != nil {
|
|
return fmt.Errorf("failed to update config %s: %w", opt.Key, err)
|
|
}
|
|
|
|
// 特殊处理:GeoIP 配置变更时刷新运行时
|
|
if opt.Key == model.ConfigKeyGeoIPProvider {
|
|
if err := geoip.RefreshRuntimeProvider(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getStatus(ctx context.Context, baseAPIPath string) *statusView {
|
|
authSources, err := publicAuthSources(ctx, baseAPIPath)
|
|
if err != nil {
|
|
authSources = []publicAuthSourceView{}
|
|
}
|
|
|
|
// 从 SystemConfig 读取配置
|
|
emailVerification, _ := repository.GetBoolByKey(ctx, model.ConfigKeyEmailLoginVerificationEnabled)
|
|
serverAddress, _ := repository.GetSystemConfigByKey(ctx, model.ConfigKeyServerAddress)
|
|
passwordRegisterEnabled, _ := repository.GetBoolByKey(ctx, model.ConfigKeyPasswordRegisterEnabled)
|
|
capLoginEnabled, _ := repository.GetBoolByKey(ctx, model.ConfigKeyCapLoginEnabled)
|
|
|
|
return &statusView{
|
|
Version: buildinfo.Version,
|
|
StartTime: model.StartTime,
|
|
EmailVerification: emailVerification,
|
|
ServerAddress: serverAddress.Value,
|
|
PasswordRegisterEnabled: passwordRegisterEnabled,
|
|
CapLoginEnabled: capLoginEnabled,
|
|
AuthSources: authSources,
|
|
}
|
|
}
|
|
|
|
func publicAuthSources(ctx context.Context, baseAPIPath string) ([]publicAuthSourceView, error) {
|
|
sources, err := repository.GetActiveAuthSources(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]publicAuthSourceView, 0, len(sources))
|
|
base := strings.TrimRight(baseAPIPath, "/")
|
|
for _, source := range sources {
|
|
result = append(result, publicAuthSourceView{
|
|
ID: source.ID,
|
|
Name: source.Name,
|
|
Type: source.Type,
|
|
DisplayName: source.DisplayName,
|
|
AuthorizeURL: fmt.Sprintf("%s/oauth/%s/authorize", base, source.Name),
|
|
IconURL: source.IconURL,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func lookupGeoIP(_ context.Context, provider, rawIP string) (*geoIPLookupView, error) {
|
|
view, err := geoip.Lookup(provider, rawIP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &geoIPLookupView{
|
|
Provider: view.Provider,
|
|
IP: view.IP,
|
|
ISOCode: view.ISOCode,
|
|
Name: view.Name,
|
|
Latitude: view.Latitude,
|
|
Longitude: view.Longitude,
|
|
}, nil
|
|
}
|
|
|
|
func syncUptimeKuma(ctx context.Context) error {
|
|
return uptimekuma.SyncToUptimeKuma(ctx)
|
|
}
|
|
|
|
// isSecretConfigKey 判断 SystemConfig 的 key 是否为敏感配置
|
|
func isSecretConfigKey(key string) bool {
|
|
return strings.Contains(key, "token") ||
|
|
strings.Contains(key, "secret") ||
|
|
strings.Contains(key, "password")
|
|
}
|