feat: add new color themes and styles for rose, violet, and yellow

- Introduced new CSS files for rose, violet, and yellow themes with custom color variables.
- Implemented dark mode styles for each theme.
- Created a color data structure to manage theme colors in the console settings.

feat: implement image cropper component

- Added an image cropper component for user profile picture editing.
- Integrated the image cropper into the user profile page.

feat: enhance console sidebar with user permissions

- Defined sidebar items with permission checks for admin and editor roles.
- Updated user center navigation to reflect user permissions.

feat: add user profile and security settings

- Developed user profile page with avatar upload and editing functionality.
- Implemented user security settings for password and email verification.

feat: create reusable dialog and OTP input components

- Built a dialog component for modal interactions.
- Developed an OTP input component for email verification.

fix: improve file handling utilities

- Added utility functions for file URI generation.
- Implemented permission checks for user roles in the common utilities.
This commit is contained in:
2025-09-20 12:45:10 +08:00
parent f8e4a84d53
commit 709aa82337
62 changed files with 1844 additions and 487 deletions

View File

@ -1,57 +1,69 @@
package constant
const (
CaptchaTypeDisable = "disable" // 禁用验证码
CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码
CaptchaTypeTurnstile = "turnstile" // Turnstile验证码
CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码
ContextKeyUserID = "user_id" // 上下文键用户ID
ContextKeyRemoteAddr = "remote_addr" // 上下文键:远程地址
ContextKeyUserAgent = "user_agent" // 上下文键:用户代理
ModeDev = "dev"
ModeProd = "prod"
RoleUser = "user" // 普通用户 仅有阅读和评论权限
RoleEditor = "editor" // 能够发布和管理自己内容的用户
RoleAdmin = "admin"
EnvKeyBaseUrl = "BASE_URL" // 环境变量基础URL
EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者
EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥
EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url
EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key
EnvKeyLocationFormat = "LOCATION_FORMAT" // 环境变量:时区格式
EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别
EnvKeyMode = "MODE" // 环境变量:运行模式
EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量JWT密钥
EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐
EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期
EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度
EnvKeyTokenDurationDefault = 30 // Token有效时长
EnvKeyRefreshTokenDurationDefault = 6000000 // refresh token有效时长
EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期
EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期
KVKeyEmailVerificationCode = "email_verification_code:" // KV存储邮箱验证码
KVKeyOidcState = "oidc_state:" // KV存储OIDC状态
ApiSuffix = "/api/v1" // API版本前缀
OidcUri = "/user/oidc/login" // OIDC登录URI
OidcProviderTypeMisskey = "misskey" // OIDC提供者类型Misskey
OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型GitHub
DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl
TargetTypePost = "post"
TargetTypeComment = "comment"
OrderByCreatedAt = "created_at" // 按创建时间排序
OrderByUpdatedAt = "updated_at" // 按更新时间排序
OrderByLikeCount = "like_count" // 按点赞数排序
OrderByCommentCount = "comment_count" // 按评论数排序
OrderByViewCount = "view_count" // 按浏览量排序
OrderByHeat = "heat"
MaxReplyDepthDefault = 3 // 默认最大回复深度
HeatFactorViewWeight = 1 // 热度因子:浏览量权重
HeatFactorLikeWeight = 5 // 热度因子:点赞权重
HeatFactorCommentWeight = 10 // 热度因子:评论权重
PageLimitDefault = 20 // 默认分页大小
CaptchaTypeDisable = "disable" // 禁用验证码
CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码
CaptchaTypeTurnstile = "turnstile" // Turnstile验证码
CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码
ContextKeyUserID = "user_id" // 上下文键用户ID
ContextKeyRemoteAddr = "remote_addr" // 上下文键:远程地址
ContextKeyUserAgent = "user_agent" // 上下文键:用户代理
ModeDev = "dev"
ModeProd = "prod"
RoleUser = "user" // 普通用户 仅有阅读和评论权限
RoleEditor = "editor" // 能够发布和管理自己内容的用户
RoleAdmin = "admin"
DefaultFileBasePath = "./data/uploads"
EnvKeyBaseUrl = "BASE_URL" // 环境变量基础URL
EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者
EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥
EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url
EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key
EnvKeyFileDriverType = "FILE_DRIVER_TYPE"
EnvKeyFileBasepath = "FILE_BASEPATH"
EnvKeyFileWebdavUrl = "FILE_WEBDAV_URL"
EnvKeyFileWebdavPassword = "FILE_WEBDAV_PASSWORD"
EnvKeyFileWebdavPolicy = "FILE_WEBDAV_POLICY"
EnvKeyFileWebdavUser = "FILE_WEBDAV_USER"
EnvKeyLocationFormat = "LOCATION_FORMAT" // 环境变量:时区格式
EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别
EnvKeyMode = "MODE" // 环境变量:运行模式
EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量JWT密钥
EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐
EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期
EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度
EnvKeyTokenDurationDefault = 500 // Token有效时长
EnvKeyRefreshTokenDurationDefault = 6000000 // refresh token有效时长
EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期
EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期
FileDriverTypeLocal = "local"
FileDriverTypeWebdav = "webdav"
FileDriverTypeS3 = "s3"
KVKeyEmailVerificationCode = "email_verification_code:" // KV存储邮箱验证码
KVKeyOidcState = "oidc_state:" // KV存储OIDC状态
ApiSuffix = "/api/v1" // API版本前缀
OidcUri = "/user/oidc/login" // OIDC登录URI
OidcProviderTypeMisskey = "misskey" // OIDC提供者类型Misskey
OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型GitHub
DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl
TargetTypePost = "post"
TargetTypeComment = "comment"
WebdavPolicyProxy = "proxy"
WebdavPolicyRedirect = "redirect"
OrderByCreatedAt = "created_at" // 按创建时间排序
OrderByUpdatedAt = "updated_at" // 按更新时间排序
OrderByLikeCount = "like_count" // 按点赞数排序
OrderByCommentCount = "comment_count" // 按评论数排序
OrderByViewCount = "view_count" // 按浏览量排序
OrderByHeat = "heat"
MaxReplyDepthDefault = 3 // 默认最大回复深度
HeatFactorViewWeight = 1 // 热度因子:浏览量权重
HeatFactorLikeWeight = 5 // 热度因子:点赞权重
HeatFactorCommentWeight = 10 // 热度因子:评论权重
PageLimitDefault = 20 // 默认分页大小
)
var (
OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式
OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式
OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式
OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式
)

View File

@ -2,8 +2,9 @@ package errs
import (
"errors"
"github.com/cloudwego/hertz/pkg/app"
"net/http"
"github.com/cloudwego/hertz/pkg/app"
)
// ServiceError 业务错误结构

View File

@ -2,10 +2,13 @@ package filedriver
import (
"fmt"
"github.com/LiteyukiStudio/spage/pkg/constants"
"github.com/cloudwego/hertz/pkg/app"
"io"
"os"
"github.com/cloudwego/hertz/pkg/app"
"github.com/snowykami/neo-blog/pkg/constant"
"github.com/snowykami/neo-blog/pkg/utils"
)
type FileDriver interface {
@ -18,19 +21,30 @@ type FileDriver interface {
}
type DriverConfig struct {
Type string `mapstructure:"file.driver.type"`
BasePath string `mapstructure:"file.driver.base_path"`
WebDavUrl string `mapstructure:"file.driver.webdav.url"`
WebDavUser string `mapstructure:"file.driver.webdav.user"`
WebDavPassword string `mapstructure:"file.driver.webdav.password"`
WebDavPolicy string `mapstructure:"file.driver.webdav.policy"` // proxy|redirect
Type string
BasePath string
WebDavUrl string
WebDavUser string
WebDavPassword string
WebDavPolicy string
}
func GetWebdavDriverConfig() *DriverConfig {
return &DriverConfig{
Type: utils.Env.Get(constant.EnvKeyFileDriverType, constant.FileDriverTypeLocal),
BasePath: utils.Env.Get(constant.EnvKeyFileBasepath, constant.DefaultFileBasePath),
WebDavUrl: utils.Env.Get(constant.EnvKeyFileWebdavUrl),
WebDavUser: utils.Env.Get(constant.EnvKeyFileWebdavUser),
WebDavPassword: utils.Env.Get(constant.EnvKeyFileWebdavPassword),
WebDavPolicy: utils.Env.Get(constant.EnvKeyFileWebdavPolicy),
}
}
func GetFileDriver(driverConfig *DriverConfig) (FileDriver, error) {
switch driverConfig.Type {
case constants.FileDriverLocal:
case constant.FileDriverTypeLocal:
return NewLocalDriver(driverConfig), nil
case constants.FileDriverWebdav:
case constant.FileDriverTypeWebdav:
return NewWebDAVClientDriver(driverConfig), nil
default:
return nil, fmt.Errorf("unsupported file driver type: %s", driverConfig.Type)

View File

@ -1,10 +1,11 @@
package filedriver
import (
"github.com/cloudwego/hertz/pkg/app"
"io"
"os"
"path/filepath"
"github.com/cloudwego/hertz/pkg/app"
)
type LocalDriver struct {

View File

@ -3,9 +3,11 @@ package filedriver
import (
"bytes"
"fmt"
"github.com/LiteyukiStudio/spage/pkg/constants"
"github.com/LiteyukiStudio/spage/pkg/resps"
"github.com/cloudwego/hertz/pkg/app"
"github.com/snowykami/neo-blog/pkg/constant"
"github.com/snowykami/neo-blog/pkg/resps"
"io"
"os"
"path"
@ -48,7 +50,7 @@ func (d *WebDAVClientDriver) Open(ctx *app.RequestContext, p string) (io.ReadClo
}
func (d *WebDAVClientDriver) Get(ctx *app.RequestContext, p string) {
if d.config.WebDavPolicy == constants.WebDavPolicyRedirect {
if d.config.WebDavPolicy == constant.WebdavPolicyRedirect {
ctx.Redirect(302, []byte(d.config.WebDavUrl+d.fullPath(p)))
return
} else {

View File

@ -1 +1,31 @@
package utils
import (
"crypto/sha256"
"encoding/hex"
"io"
"mime/multipart"
)
// FilePath 根据哈希值生成文件路径前4位为目录位hash[0:4]/hash
func FilePath(hash string) (dir, file string) {
dir = hash[0:4]
file = hash
return
}
func FileHashFromStream(file multipart.File) (string, error) {
// 创建哈希计算器
hash := sha256.New()
// 将文件流内容拷贝到哈希计算器
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
// 计算哈希值并转换为十六进制字符串
hashInBytes := hash.Sum(nil)
hashString := hex.EncodeToString(hashInBytes)
return hashString, nil
}