wavelet init

This commit is contained in:
ryan
2026-06-18 15:24:48 +08:00
parent d6a7011885
commit 99738bbc17
714 changed files with 139987 additions and 0 deletions
@@ -0,0 +1,76 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package config
import (
"context"
"testing"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/testhelper"
)
func TestListVisibleSystemConfigsUsesRedisCache(t *testing.T) {
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
defer cleanup()
ctx := context.Background()
if err := repository.InvalidateVisibleSystemConfigsCache(ctx); err != nil {
t.Fatalf("InvalidateVisibleSystemConfigsCache() error = %v", err)
}
if _, err := repository.ListVisibleSystemConfigs(ctx); err != nil {
t.Fatalf("ListVisibleSystemConfigs() warm cache error = %v", err)
}
if err := dbConn.Create(&model.SystemConfig{
Key: "cache_probe_public_key",
Value: "cache_probe_public_value",
Type: "system",
Visibility: model.ConfigVisibilityVisible,
Description: "cache probe",
}).Error; err != nil {
t.Fatalf("Create(cache_probe_public_key) error = %v", err)
}
cached, err := repository.ListVisibleSystemConfigs(ctx)
if err != nil {
t.Fatalf("ListVisibleSystemConfigs() cached call error = %v", err)
}
for _, item := range cached {
if item.Key == "cache_probe_public_key" {
t.Fatal("cached visible config list should be stale before invalidation")
}
}
exists, err := db.Redis.Exists(ctx, db.PrefixedKey(repository.SystemConfigVisibleListRedisKey)).Result()
if err != nil {
t.Fatalf("Redis.Exists() error = %v", err)
}
if exists == 0 {
t.Fatal("expected visible config list cache key to exist")
}
if err := repository.InvalidateVisibleSystemConfigsCache(ctx); err != nil {
t.Fatalf("InvalidateVisibleSystemConfigsCache() error = %v", err)
}
refreshed, err := repository.ListVisibleSystemConfigs(ctx)
if err != nil {
t.Fatalf("ListVisibleSystemConfigs() refreshed call error = %v", err)
}
var found bool
for _, item := range refreshed {
if item.Key == "cache_probe_public_key" {
found = true
break
}
}
if !found {
t.Fatal("refreshed visible config list should include newly created public config")
}
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2025 linux.do
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package config 提供公开配置查询接口
package config
import (
"net/http"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/gin-gonic/gin"
"github.com/Rain-kl/Wavelet/internal/common/response"
)
// GetPublicConfig 获取公共配置
// @Summary 获取公共配置
// @Description 返回系统配置表中 visibility 为 1 的配置键值集合
// @Tags config
// @Accept json
// @Produce json
// @Success 200 {object} response.Any
// @Router /api/v1/config/public [get]
func GetPublicConfig(c *gin.Context) {
ctx := c.Request.Context()
configs, err := repository.ListVisibleSystemConfigs(ctx)
if err != nil {
response.AbortInternal(c, err.Error())
return
}
resp := make(map[string]string, len(configs))
for _, config := range configs {
resp[config.Key] = config.Value
}
c.JSON(http.StatusOK, response.OK(resp))
}
// GetRobotsTXT 动态生成 robots.txt
// @Summary 获取 robots.txt
// @Description 根据系统配置决定是否允许搜索引擎检索,并返回相应的 robots.txt 文件内容
// @Tags config
// @Produce text/plain
// @Success 200 {string} string "robots.txt 内容"
// @Router /robots.txt [get]
func GetRobotsTXT(c *gin.Context) {
ctx := c.Request.Context()
enabled, err := repository.GetBoolByKey(ctx, model.ConfigKeySearchEngineIndexingEnabled)
content := "User-Agent: *\nDisallow: /\n"
if err == nil && enabled {
content = "User-Agent: *\nAllow: /\n"
}
c.Data(http.StatusOK, "text/plain; charset=utf-8", []byte(content))
}
@@ -0,0 +1,70 @@
// Copyright 2025 linux.do
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package config
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/testhelper"
"github.com/gin-gonic/gin"
"github.com/Rain-kl/Wavelet/internal/common/response"
)
func TestGetPublicConfigUsesVisibility(t *testing.T) {
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
defer cleanup()
if err := dbConn.Create(&model.SystemConfig{
Key: "custom_public_key",
Value: "custom_public_value",
Type: "system",
Visibility: model.ConfigVisibilityVisible,
Description: "custom public config",
}).Error; err != nil {
t.Fatalf("Create(custom_public_key) error = %v", err)
}
if err := dbConn.Model(&model.SystemConfig{}).
Where("key = ?", model.ConfigKeySiteName).
Update("visibility", model.ConfigVisibilityHidden).Error; err != nil {
t.Fatalf("Update(%s.visibility) error = %v", model.ConfigKeySiteName, err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/api/v1/config/public", GetPublicConfig)
req := httptest.NewRequest(http.MethodGet, "/api/v1/config/public", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("GetPublicConfig() status = %d, want %d; body = %s", w.Code, http.StatusOK, w.Body.String())
}
var resp response.Any
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("json.Unmarshal(GetPublicConfig()) error = %v", err)
}
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
t.Fatalf("json.Marshal(GetPublicConfig().data) error = %v", err)
}
var configs map[string]string
if err := json.Unmarshal(dataBytes, &configs); err != nil {
t.Fatalf("json.Unmarshal(GetPublicConfig().data) error = %v", err)
}
if got := configs["custom_public_key"]; got != "custom_public_value" {
t.Errorf("GetPublicConfig()[custom_public_key] = %q, want %q", got, "custom_public_value")
}
if _, ok := configs[model.ConfigKeySiteName]; ok {
t.Errorf("GetPublicConfig()[%s] is present, want hidden", model.ConfigKeySiteName)
}
}
@@ -0,0 +1,108 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package config
import (
"context"
"errors"
"testing"
"github.com/redis/go-redis/v9"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/testhelper"
)
func TestSystemConfigRAMCacheServesUntilInvalidated(t *testing.T) {
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
defer cleanup()
ctx := context.Background()
repository.ResetSystemConfigRAMCacheForTest()
if err := repository.InvalidateAllSystemConfigCaches(ctx); err != nil {
t.Fatalf("InvalidateAllSystemConfigCaches() error = %v", err)
}
warm, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeySiteName)
if err != nil {
t.Fatalf("GetSystemConfigByKey(site_name) warm error = %v", err)
}
if warm.Value != "Wavelet" {
t.Fatalf("GetSystemConfigByKey(site_name).Value = %q, want %q", warm.Value, "Wavelet")
}
if err := dbConn.Model(&model.SystemConfig{}).
Where("key = ?", model.ConfigKeySiteName).
Update("value", "ram_probe_value").Error; err != nil {
t.Fatalf("Update(site_name) error = %v", err)
}
if err := db.HDel(ctx, repository.SystemConfigRedisHashKey, model.ConfigKeySiteName); err != nil {
t.Fatalf("HDel(site_name) error = %v", err)
}
cached, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeySiteName)
if err != nil {
t.Fatalf("GetSystemConfigByKey(site_name) cached error = %v", err)
}
if cached.Value != "Wavelet" {
t.Fatalf("GetSystemConfigByKey(site_name).Value = %q, want stale RAM value %q", cached.Value, "Wavelet")
}
if err := repository.InvalidateSystemConfigCache(ctx, model.ConfigKeySiteName); err != nil {
t.Fatalf("InvalidateSystemConfigCache(site_name) error = %v", err)
}
refreshed, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeySiteName)
if err != nil {
t.Fatalf("GetSystemConfigByKey(site_name) refreshed error = %v", err)
}
if refreshed.Value != "ram_probe_value" {
t.Fatalf("GetSystemConfigByKey(site_name).Value = %q, want %q", refreshed.Value, "ram_probe_value")
}
exists, err := db.Redis.HExists(ctx, db.PrefixedKey(repository.SystemConfigRedisHashKey), model.ConfigKeySiteName).Result()
if err != nil {
t.Fatalf("HExists(site_name) error = %v", err)
}
if !exists {
t.Fatal("expected redis hash field to be repopulated after refresh")
}
}
func TestInvalidateSystemConfigCacheClearsRedisField(t *testing.T) {
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
defer cleanup()
ctx := context.Background()
sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeySiteName)
if err != nil {
t.Fatalf("GetSystemConfigByKey(site_name) error = %v", err)
}
_ = sc
if err := repository.InvalidateSystemConfigCache(ctx, model.ConfigKeySiteName); err != nil {
t.Fatalf("InvalidateSystemConfigCache(site_name) error = %v", err)
}
_, err = db.Redis.HGet(ctx, db.PrefixedKey(repository.SystemConfigRedisHashKey), model.ConfigKeySiteName).Result()
if !errors.Is(err, redis.Nil) {
t.Fatalf("HGet(site_name) error = %v, want redis.Nil", err)
}
if err := dbConn.Model(&model.SystemConfig{}).
Where("key = ?", model.ConfigKeySiteName).
Update("value", "after_invalidate").Error; err != nil {
t.Fatalf("Update(site_name) error = %v", err)
}
refreshed, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeySiteName)
if err != nil {
t.Fatalf("GetSystemConfigByKey(site_name) refreshed error = %v", err)
}
if refreshed.Value != "after_invalidate" {
t.Fatalf("GetSystemConfigByKey(site_name).Value = %q, want %q", refreshed.Value, "after_invalidate")
}
}