mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 19:16:24 +00:00
Refactor site configuration and color scheme management
- Replaced static config with dynamic site info context. - Updated color scheme handling in various components to use site info. - Removed deprecated config file and integrated site info fetching. - Enhanced user preference page to allow color scheme selection. - Adjusted blog and console components to reflect new site info structure. - Improved error handling and fallback mechanisms for site info retrieval.
This commit is contained in:
@ -1 +1,64 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/common/utils"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
)
|
||||
|
||||
const (
|
||||
KeySiteInfo = "site_info"
|
||||
)
|
||||
|
||||
type MiscController struct{}
|
||||
|
||||
func NewMiscController() *MiscController {
|
||||
return &MiscController{}
|
||||
}
|
||||
|
||||
func (mc *MiscController) GetSiteInfo(ctx context.Context, c *app.RequestContext) {
|
||||
value, err := repo.KV.GetKV(KeySiteInfo, utils.H{
|
||||
"metadata": utils.H{
|
||||
"name": "Neo Blog S",
|
||||
"icon": "https://cdn.liteyuki.org/snowykami/avatar.jpg",
|
||||
"description": "A neo blog system.",
|
||||
},
|
||||
"color_schemes": []string{"blue", "green", "orange", "red", "rose", "violet", "yellow"},
|
||||
"default_cover": "https://cdn.liteyuki.org/blog/background.png",
|
||||
"owner": utils.H{
|
||||
"name": "SnowyKami",
|
||||
"description": "A full-stack developer.",
|
||||
"avatar": "https://cdn.liteyuki.org/snowykami/avatar.jpg",
|
||||
},
|
||||
"posts_per_page": 9,
|
||||
"comments_per_page": 8,
|
||||
"verify_code_cool_down": 60,
|
||||
"animation_duration_second": 0.618,
|
||||
"footer": utils.H{
|
||||
"text": "Liteyuki ICP 114514",
|
||||
"links": []string{"https://www.liteyuki.com/"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
resps.Ok(c, "", value)
|
||||
}
|
||||
|
||||
func (mc *MiscController) SetSiteInfo(ctx context.Context, c *app.RequestContext) {
|
||||
data := make(map[string]interface{})
|
||||
err := c.BindAndValidate(&data)
|
||||
if err != nil {
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = repo.KV.SetKV(KeySiteInfo, data)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, err.Error())
|
||||
}
|
||||
resps.Ok(c, "", nil)
|
||||
}
|
||||
|
@ -1,102 +1,104 @@
|
||||
package dto
|
||||
|
||||
type UserDto struct {
|
||||
ID uint `json:"id"` // 用户ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"` // 头像URL
|
||||
BackgroundUrl string `json:"background_url"`
|
||||
Email string `json:"email"` // 邮箱
|
||||
Gender string `json:"gender"`
|
||||
Role string `json:"role"`
|
||||
Language string `json:"language"` // 语言
|
||||
ID uint `json:"id"` // 用户ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"` // 头像URL
|
||||
BackgroundUrl string `json:"background_url"`
|
||||
PreferredColor string `json:"preferred_color"` // 主题色
|
||||
Email string `json:"email"` // 邮箱
|
||||
Gender string `json:"gender"`
|
||||
Role string `json:"role"`
|
||||
Language string `json:"language"` // 语言
|
||||
}
|
||||
|
||||
type UserOidcConfigDto struct {
|
||||
Name string `json:"name"` // OIDC配置名称
|
||||
DisplayName string `json:"display_name"` // OIDC配置显示名称
|
||||
Icon string `json:"icon"` // OIDC配置图标URL
|
||||
LoginUrl string `json:"login_url"` // OIDC登录URL
|
||||
Name string `json:"name"` // OIDC配置名称
|
||||
DisplayName string `json:"display_name"` // OIDC配置显示名称
|
||||
Icon string `json:"icon"` // OIDC配置图标URL
|
||||
LoginUrl string `json:"login_url"` // OIDC登录URL
|
||||
}
|
||||
type UserLoginReq struct {
|
||||
Username string `json:"username"` // username or email
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"` // username or email
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type UserLoginResp struct {
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User UserDto `json:"user"`
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User UserDto `json:"user"`
|
||||
}
|
||||
|
||||
type UserRegisterReq struct {
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Password string `json:"password"` // 密码
|
||||
Email string `json:"-" binding:"-"`
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Password string `json:"password"` // 密码
|
||||
Email string `json:"-" binding:"-"`
|
||||
}
|
||||
|
||||
type UserRegisterResp struct {
|
||||
Token string `json:"token"` // 访问令牌
|
||||
RefreshToken string `json:"refresh_token"` // 刷新令牌
|
||||
User UserDto `json:"user"` // 用户信息
|
||||
Token string `json:"token"` // 访问令牌
|
||||
RefreshToken string `json:"refresh_token"` // 刷新令牌
|
||||
User UserDto `json:"user"` // 用户信息
|
||||
}
|
||||
|
||||
type VerifyEmailReq struct {
|
||||
Email string `json:"email"` // 邮箱地址
|
||||
Email string `json:"email"` // 邮箱地址
|
||||
}
|
||||
|
||||
type VerifyEmailResp struct {
|
||||
Success bool `json:"success"` // 验证码发送成功与否
|
||||
Success bool `json:"success"` // 验证码发送成功与否
|
||||
}
|
||||
|
||||
type OidcLoginReq struct {
|
||||
Name string `json:"name"` // OIDC配置名称
|
||||
Code string `json:"code"` // OIDC授权码
|
||||
State string `json:"state"`
|
||||
Name string `json:"name"` // OIDC配置名称
|
||||
Code string `json:"code"` // OIDC授权码
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type OidcLoginResp struct {
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User UserDto `json:"user"`
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User UserDto `json:"user"`
|
||||
}
|
||||
|
||||
type ListOidcConfigResp struct {
|
||||
OidcConfigs []UserOidcConfigDto `json:"oidc_configs"` // OIDC配置列表
|
||||
OidcConfigs []UserOidcConfigDto `json:"oidc_configs"` // OIDC配置列表
|
||||
}
|
||||
|
||||
type GetUserReq struct {
|
||||
UserID uint `json:"user_id"`
|
||||
UserID uint `json:"user_id"`
|
||||
}
|
||||
|
||||
type GetUserByUsernameReq struct {
|
||||
Username string `json:"username"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type GetUserResp struct {
|
||||
User UserDto `json:"user"` // 用户信息
|
||||
User UserDto `json:"user"` // 用户信息
|
||||
}
|
||||
|
||||
type UpdateUserReq struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
BackgroundUrl string `json:"background_url"`
|
||||
Gender string `json:"gender"`
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
BackgroundUrl string `json:"background_url"`
|
||||
PreferredColor string `json:"preferred_color"`
|
||||
Gender string `json:"gender"`
|
||||
}
|
||||
|
||||
type UpdateUserResp struct {
|
||||
User *UserDto `json:"user"` // 更新后的用户信息
|
||||
User *UserDto `json:"user"` // 更新后的用户信息
|
||||
}
|
||||
|
||||
type UpdatePasswordReq struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
type ResetPasswordReq struct {
|
||||
Email string `json:"-" binding:"-"`
|
||||
NewPassword string `json:"new_password"`
|
||||
Email string `json:"-" binding:"-"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
@ -1 +1,84 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// JSONMap 是一个通用的 JSON 类型(map[string]any)
|
||||
type JSONMap map[string]any
|
||||
|
||||
func (JSONMap) GormDataType() string {
|
||||
return "json"
|
||||
}
|
||||
|
||||
func (JSONMap) GormDBDataType(db *gorm.DB, _ *schema.Field) string {
|
||||
switch db.Dialector.Name() {
|
||||
case "mysql":
|
||||
return "JSON"
|
||||
case "postgres":
|
||||
return "JSONB"
|
||||
default: // sqlite 等
|
||||
return "TEXT"
|
||||
}
|
||||
}
|
||||
|
||||
// Value 实现 driver.Valuer,用于写入数据库
|
||||
func (j JSONMap) Value() (driver.Value, error) {
|
||||
if j == nil {
|
||||
return nil, nil
|
||||
}
|
||||
b, err := json.Marshal(map[string]any(j))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// Scan 实现 sql.Scanner,用于从数据库读取并反序列化
|
||||
func (j *JSONMap) Scan(src any) error {
|
||||
if src == nil {
|
||||
*j = nil
|
||||
return nil
|
||||
}
|
||||
var data []byte
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
data = []byte(v)
|
||||
case []byte:
|
||||
data = v
|
||||
default:
|
||||
return fmt.Errorf("cannot scan JSONMap from %T", src)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
*j = nil
|
||||
return nil
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = JSONMap(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j JSONMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any(j))
|
||||
}
|
||||
|
||||
func (j *JSONMap) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 || string(b) == "null" {
|
||||
*j = nil
|
||||
return nil
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = JSONMap(m)
|
||||
return nil
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
package model
|
||||
|
||||
type KV struct {
|
||||
Key string `gorm:"primaryKey;type:varchar(64);not null;comment:键"`
|
||||
Value JSONMap
|
||||
}
|
||||
|
@ -1,41 +1,43 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"gorm.io/gorm"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string `gorm:"uniqueIndex;not null"` // 用户名,唯一
|
||||
Nickname string `gorm:"default:''"` // 昵称
|
||||
AvatarUrl string
|
||||
BackgroundUrl string
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
Gender string `gorm:"default:''"`
|
||||
Role string `gorm:"default:'user'"` // user editor admin
|
||||
Language string `gorm:"default:'en'"`
|
||||
Password string // 密码,存储加密后的值
|
||||
gorm.Model
|
||||
Username string `gorm:"uniqueIndex;not null"` // 用户名,唯一
|
||||
Nickname string `gorm:"default:''"` // 昵称
|
||||
AvatarUrl string
|
||||
BackgroundUrl string
|
||||
PreferredColor string `gorm:"default:'global'"` // 主题色
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
Gender string `gorm:"default:''"`
|
||||
Role string `gorm:"default:'user'"` // user editor admin
|
||||
Language string `gorm:"default:'en'"`
|
||||
Password string // 密码,存储加密后的值
|
||||
}
|
||||
|
||||
type UserOpenID struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"index"`
|
||||
User User `gorm:"foreignKey:UserID;references:ID"`
|
||||
Issuer string `gorm:"index"` // OIDC Issuer
|
||||
Sub string `gorm:"index"` // OIDC Sub openid
|
||||
gorm.Model
|
||||
UserID uint `gorm:"index"`
|
||||
User User `gorm:"foreignKey:UserID;references:ID"`
|
||||
Issuer string `gorm:"index"` // OIDC Issuer
|
||||
Sub string `gorm:"index"` // OIDC Sub openid
|
||||
}
|
||||
|
||||
func (user *User) ToDto() dto.UserDto {
|
||||
return dto.UserDto{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
AvatarUrl: user.AvatarUrl,
|
||||
BackgroundUrl: user.BackgroundUrl,
|
||||
Email: user.Email,
|
||||
Gender: user.Gender,
|
||||
Role: user.Role,
|
||||
Language: user.Language,
|
||||
}
|
||||
return dto.UserDto{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
AvatarUrl: user.AvatarUrl,
|
||||
BackgroundUrl: user.BackgroundUrl,
|
||||
PreferredColor: user.PreferredColor,
|
||||
Email: user.Email,
|
||||
Gender: user.Gender,
|
||||
Role: user.Role,
|
||||
Language: user.Language,
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +129,7 @@ func migrate() error {
|
||||
&model.Label{},
|
||||
&model.Like{},
|
||||
&model.File{},
|
||||
&model.KV{},
|
||||
&model.OidcConfig{},
|
||||
&model.Post{},
|
||||
&model.Session{},
|
||||
|
@ -1,5 +1,50 @@
|
||||
package main
|
||||
package repo
|
||||
|
||||
func main() {
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type kvRepo struct{}
|
||||
|
||||
var KV = &kvRepo{}
|
||||
|
||||
func (k *kvRepo) GetKV(key string, defaultValue ...any) (any, error) {
|
||||
var kv = &model.KV{}
|
||||
err := GetDB().First(kv, "key = ?", key).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if len(defaultValue) > 0 {
|
||||
return defaultValue[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kv.Value == nil {
|
||||
if len(defaultValue) > 0 {
|
||||
return defaultValue[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if v, ok := kv.Value["value"]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
if len(defaultValue) > 0 {
|
||||
return defaultValue[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (k *kvRepo) SetKV(key string, value any) error {
|
||||
kv := &model.KV{
|
||||
Key: key,
|
||||
Value: map[string]any{"value": value},
|
||||
}
|
||||
return GetDB().Save(kv).Error
|
||||
}
|
||||
|
@ -1 +1,18 @@
|
||||
package repo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestKvRepo_GetKV(t *testing.T) {
|
||||
err := KV.SetKV("AAA", map[string]interface{}{"b": 1, "c": "2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, _ := KV.GetKV("AAA")
|
||||
t.Log(v)
|
||||
if v.(map[string]interface{})["b"] != float64(1) {
|
||||
t.Fatal("b not equal")
|
||||
}
|
||||
if v.(map[string]interface{})["c"] != "2" {
|
||||
t.Fatal("c not equal")
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,18 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
)
|
||||
|
||||
func registerMiscRoutes(group *route.RouterGroup) {
|
||||
miscController := v1.NewMiscController()
|
||||
miscGroupAdmin := group.Group("/misc").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleAdmin))
|
||||
miscGroupWithoutAuth := group.Group("/misc").Use(middleware.UseAuth(false))
|
||||
{
|
||||
miscGroupWithoutAuth.GET("/site-info", miscController.GetSiteInfo)
|
||||
miscGroupAdmin.PUT("/site-info", miscController.SetSiteInfo)
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,16 @@ package apiv1
|
||||
import "github.com/cloudwego/hertz/pkg/app/server"
|
||||
|
||||
func RegisterRoutes(h *server.Hertz) {
|
||||
apiV1Group := h.Group("/api/v1")
|
||||
{
|
||||
registerCommentRoutes(apiV1Group)
|
||||
registerAdminRoutes(apiV1Group)
|
||||
registerFileRoutes(apiV1Group)
|
||||
registerLabelRoutes(apiV1Group)
|
||||
registerLikeRoutes(apiV1Group)
|
||||
registerPageRoutes(apiV1Group)
|
||||
registerPostRoutes(apiV1Group)
|
||||
registerUserRoutes(apiV1Group)
|
||||
}
|
||||
apiV1Group := h.Group("/api/v1")
|
||||
{
|
||||
registerCommentRoutes(apiV1Group)
|
||||
registerAdminRoutes(apiV1Group)
|
||||
registerFileRoutes(apiV1Group)
|
||||
registerLabelRoutes(apiV1Group)
|
||||
registerLikeRoutes(apiV1Group)
|
||||
registerMiscRoutes(apiV1Group)
|
||||
registerPageRoutes(apiV1Group)
|
||||
registerPostRoutes(apiV1Group)
|
||||
registerUserRoutes(apiV1Group)
|
||||
}
|
||||
}
|
||||
|
@ -355,10 +355,12 @@ func (s *UserService) UpdateUser(req *dto.UpdateUserReq) (*dto.UpdateUserResp, e
|
||||
Model: gorm.Model{
|
||||
ID: req.ID,
|
||||
},
|
||||
Username: req.Username,
|
||||
Nickname: req.Nickname,
|
||||
Gender: req.Gender,
|
||||
AvatarUrl: req.AvatarUrl,
|
||||
Username: req.Username,
|
||||
Nickname: req.Nickname,
|
||||
Gender: req.Gender,
|
||||
AvatarUrl: req.AvatarUrl,
|
||||
BackgroundUrl: req.BackgroundUrl,
|
||||
PreferredColor: req.PreferredColor,
|
||||
}
|
||||
err := repo.User.UpdateUser(user)
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user