Compare commits
2 Commits
8a0792500e
...
5dd0bc19e4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5dd0bc19e4 | |||
| 48b6a5f4aa |
31
admin/.eslintrc.json
Normal file
31
admin/.eslintrc.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2020": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"*.config.*"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
|
||||||
|
],
|
||||||
|
"no-console": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import React from "react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Skeleton({
|
function Skeleton({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useRef, useEffect } from 'react'
|
import React, { useState, useCallback, useRef, useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|||||||
@ -29,8 +29,8 @@ func main() {
|
|||||||
|
|
||||||
// 添加静态文件服务
|
// 添加静态文件服务
|
||||||
server.AddRoute(rest.Route{
|
server.AddRoute(rest.Route{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/uploads/*",
|
Path: "/uploads/*",
|
||||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads"))).ServeHTTP(w, r)
|
http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads"))).ServeHTTP(w, r)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
rest.RestConf
|
rest.RestConf
|
||||||
Database database.Config `json:"database"`
|
Database database.Config `json:"database"`
|
||||||
Auth AuthConfig `json:"auth"`
|
Auth AuthConfig `json:"auth"`
|
||||||
FileUpload FileUploadConfig `json:"file_upload"`
|
FileUpload FileUploadConfig `json:"file_upload"`
|
||||||
Middleware MiddlewareConfig `json:"middleware"`
|
Middleware MiddlewareConfig `json:"middleware"`
|
||||||
}
|
}
|
||||||
@ -28,6 +28,6 @@ type MiddlewareConfig struct {
|
|||||||
EnableCORS bool `json:"enable_cors"`
|
EnableCORS bool `json:"enable_cors"`
|
||||||
EnableLogger bool `json:"enable_logger"`
|
EnableLogger bool `json:"enable_logger"`
|
||||||
EnableErrorHandle bool `json:"enable_error_handle"`
|
EnableErrorHandle bool `json:"enable_error_handle"`
|
||||||
CORSOrigins []string `json:"cors_origins"`
|
CORSOrigins []string `json:"cors_origins"`
|
||||||
LogLevel string `json:"log_level"`
|
LogLevel string `json:"log_level"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,10 +49,10 @@ func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.Regist
|
|||||||
|
|
||||||
// 4. 创建用户
|
// 4. 创建用户
|
||||||
user := &model.User{
|
user := &model.User{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Password: hashedPassword,
|
Password: hashedPassword,
|
||||||
Status: 1, // 默认激活状态
|
Status: 1, // 默认激活状态
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http
|
|||||||
// 4. 获取上传的文件
|
// 4. 获取上传的文件
|
||||||
uploadedFile, header, err := r.FormFile("avatar")
|
uploadedFile, header, err := r.FormFile("avatar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.New(errorx.ParamError, "获取上传文件失败: " + err.Error())
|
return nil, errorx.New(errorx.ParamError, "获取上传文件失败: "+err.Error())
|
||||||
}
|
}
|
||||||
defer uploadedFile.Close()
|
defer uploadedFile.Close()
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http
|
|||||||
|
|
||||||
// 8. 确保头像目录存在
|
// 8. 确保头像目录存在
|
||||||
if err := os.MkdirAll(avatarDir, 0755); err != nil {
|
if err := os.MkdirAll(avatarDir, 0755); err != nil {
|
||||||
return nil, errorx.New(errorx.ServerError, "创建头像目录失败: " + err.Error())
|
return nil, errorx.New(errorx.ServerError, "创建头像目录失败: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarPath := filepath.Join(avatarDir, filename)
|
avatarPath := filepath.Join(avatarDir, filename)
|
||||||
@ -101,13 +101,13 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http
|
|||||||
// 9. 保存原始头像文件
|
// 9. 保存原始头像文件
|
||||||
destFile, err := os.Create(avatarPath)
|
destFile, err := os.Create(avatarPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.New(errorx.ServerError, "创建头像文件失败: " + err.Error())
|
return nil, errorx.New(errorx.ServerError, "创建头像文件失败: "+err.Error())
|
||||||
}
|
}
|
||||||
defer destFile.Close()
|
defer destFile.Close()
|
||||||
|
|
||||||
_, err = io.Copy(destFile, uploadedFile)
|
_, err = io.Copy(destFile, uploadedFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.New(errorx.ServerError, "保存头像文件失败: " + err.Error())
|
return nil, errorx.New(errorx.ServerError, "保存头像文件失败: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. 生成压缩版本的头像 (150x150像素)
|
// 10. 生成压缩版本的头像 (150x150像素)
|
||||||
@ -142,7 +142,7 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http
|
|||||||
if avatarPath != compressedPath {
|
if avatarPath != compressedPath {
|
||||||
os.Remove(compressedPath)
|
os.Remove(compressedPath)
|
||||||
}
|
}
|
||||||
return nil, errorx.New(errorx.ServerError, "更新用户头像失败: " + err.Error())
|
return nil, errorx.New(errorx.ServerError, "更新用户头像失败: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.UploadAvatarResponse{
|
return &types.UploadAvatarResponse{
|
||||||
|
|||||||
@ -19,8 +19,8 @@ type ErrorConfig struct {
|
|||||||
EnableDetailedErrors bool // 是否启用详细错误信息 (开发环境)
|
EnableDetailedErrors bool // 是否启用详细错误信息 (开发环境)
|
||||||
EnableStackTrace bool // 是否启用堆栈跟踪
|
EnableStackTrace bool // 是否启用堆栈跟踪
|
||||||
EnableErrorMonitor bool // 是否启用错误监控
|
EnableErrorMonitor bool // 是否启用错误监控
|
||||||
IgnoreHTTPCodes []int // 忽略的HTTP状态码 (不记录为错误)
|
IgnoreHTTPCodes []int // 忽略的HTTP状态码 (不记录为错误)
|
||||||
SensitiveFields []string // 敏感字段列表 (日志时隐藏)
|
SensitiveFields []string // 敏感字段列表 (日志时隐藏)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultErrorConfig 默认错误配置
|
// DefaultErrorConfig 默认错误配置
|
||||||
@ -29,8 +29,8 @@ func DefaultErrorConfig() ErrorConfig {
|
|||||||
EnableDetailedErrors: false, // 生产环境默认关闭
|
EnableDetailedErrors: false, // 生产环境默认关闭
|
||||||
EnableStackTrace: false, // 生产环境默认关闭
|
EnableStackTrace: false, // 生产环境默认关闭
|
||||||
EnableErrorMonitor: true,
|
EnableErrorMonitor: true,
|
||||||
IgnoreHTTPCodes: []int{http.StatusNotFound, http.StatusMethodNotAllowed},
|
IgnoreHTTPCodes: []int{http.StatusNotFound, http.StatusMethodNotAllowed},
|
||||||
SensitiveFields: []string{"password", "token", "secret", "key", "authorization"},
|
SensitiveFields: []string{"password", "token", "secret", "key", "authorization"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
// LoggerConfig 日志配置
|
// LoggerConfig 日志配置
|
||||||
type LoggerConfig struct {
|
type LoggerConfig struct {
|
||||||
EnableRequestBody bool // 是否记录请求体
|
EnableRequestBody bool // 是否记录请求体
|
||||||
EnableResponseBody bool // 是否记录响应体
|
EnableResponseBody bool // 是否记录响应体
|
||||||
MaxBodySize int64 // 最大记录的请求/响应体大小
|
MaxBodySize int64 // 最大记录的请求/响应体大小
|
||||||
SkipPaths []string // 跳过记录的路径
|
SkipPaths []string // 跳过记录的路径
|
||||||
SlowRequestDuration time.Duration // 慢请求阈值
|
SlowRequestDuration time.Duration // 慢请求阈值
|
||||||
@ -26,9 +26,9 @@ type LoggerConfig struct {
|
|||||||
// DefaultLoggerConfig 默认日志配置
|
// DefaultLoggerConfig 默认日志配置
|
||||||
func DefaultLoggerConfig() LoggerConfig {
|
func DefaultLoggerConfig() LoggerConfig {
|
||||||
return LoggerConfig{
|
return LoggerConfig{
|
||||||
EnableRequestBody: false, // 默认不记录请求体 (可能包含敏感信息)
|
EnableRequestBody: false, // 默认不记录请求体 (可能包含敏感信息)
|
||||||
EnableResponseBody: false, // 默认不记录响应体 (减少日志量)
|
EnableResponseBody: false, // 默认不记录响应体 (减少日志量)
|
||||||
MaxBodySize: 1024, // 最大记录1KB
|
MaxBodySize: 1024, // 最大记录1KB
|
||||||
SkipPaths: []string{"/health", "/metrics", "/favicon.ico"},
|
SkipPaths: []string{"/health", "/metrics", "/favicon.ico"},
|
||||||
SlowRequestDuration: 1 * time.Second,
|
SlowRequestDuration: 1 * time.Second,
|
||||||
EnablePanicRecover: true,
|
EnablePanicRecover: true,
|
||||||
@ -60,7 +60,7 @@ func newResponseWriter(w http.ResponseWriter) *responseWriter {
|
|||||||
return &responseWriter{
|
return &responseWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
body: &bytes.Buffer{},
|
body: &bytes.Buffer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,13 +208,13 @@ func (m *LoggerMiddleware) logRequestStart(r *http.Request, requestID, requestBo
|
|||||||
// logRequestComplete 记录请求完成
|
// logRequestComplete 记录请求完成
|
||||||
func (m *LoggerMiddleware) logRequestComplete(r *http.Request, requestID string, status int, size int64, duration time.Duration, responseBody string) {
|
func (m *LoggerMiddleware) logRequestComplete(r *http.Request, requestID string, status int, size int64, duration time.Duration, responseBody string) {
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"request_id": requestID,
|
"request_id": requestID,
|
||||||
"method": r.Method,
|
"method": r.Method,
|
||||||
"path": r.URL.Path,
|
"path": r.URL.Path,
|
||||||
"status": status,
|
"status": status,
|
||||||
"response_size": size,
|
"response_size": size,
|
||||||
"duration_ms": duration.Milliseconds(),
|
"duration_ms": duration.Milliseconds(),
|
||||||
"duration": duration.String(),
|
"duration": duration.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseBody != "" {
|
if responseBody != "" {
|
||||||
|
|||||||
@ -13,11 +13,11 @@ import (
|
|||||||
|
|
||||||
// MiddlewareManager 中间件管理器
|
// MiddlewareManager 中间件管理器
|
||||||
type MiddlewareManager struct {
|
type MiddlewareManager struct {
|
||||||
config config.Config
|
config config.Config
|
||||||
corsMiddleware *CORSMiddleware
|
corsMiddleware *CORSMiddleware
|
||||||
logMiddleware *LoggerMiddleware
|
logMiddleware *LoggerMiddleware
|
||||||
errorMiddleware *ErrorMiddleware
|
errorMiddleware *ErrorMiddleware
|
||||||
authMiddleware *AuthMiddleware
|
authMiddleware *AuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMiddlewareManager 创建中间件管理器
|
// NewMiddlewareManager 创建中间件管理器
|
||||||
@ -108,9 +108,9 @@ func (m *MiddlewareManager) Chain(handler http.HandlerFunc, middlewares ...func(
|
|||||||
// GetGlobalMiddlewares 获取全局中间件
|
// GetGlobalMiddlewares 获取全局中间件
|
||||||
func (m *MiddlewareManager) GetGlobalMiddlewares() []func(http.HandlerFunc) http.HandlerFunc {
|
func (m *MiddlewareManager) GetGlobalMiddlewares() []func(http.HandlerFunc) http.HandlerFunc {
|
||||||
return []func(http.HandlerFunc) http.HandlerFunc{
|
return []func(http.HandlerFunc) http.HandlerFunc{
|
||||||
m.errorMiddleware.Handle, // 错误处理 (最外层)
|
m.errorMiddleware.Handle, // 错误处理 (最外层)
|
||||||
m.corsMiddleware.Handle, // CORS 处理
|
m.corsMiddleware.Handle, // CORS 处理
|
||||||
m.logMiddleware.Handle, // 日志记录
|
m.logMiddleware.Handle, // 日志记录
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ CategoryModel = (*customCategoryModel)(nil)
|
var _ CategoryModel = (*customCategoryModel)(nil)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ PhotoModel = (*customPhotoModel)(nil)
|
var _ PhotoModel = (*customPhotoModel)(nil)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ UserModel = (*customUserModel)(nil)
|
var _ UserModel = (*customUserModel)(nil)
|
||||||
|
|||||||
@ -2,17 +2,17 @@ package svc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"photography-backend/internal/config"
|
"photography-backend/internal/config"
|
||||||
"photography-backend/internal/middleware"
|
"photography-backend/internal/middleware"
|
||||||
"photography-backend/internal/model"
|
"photography-backend/internal/model"
|
||||||
"photography-backend/pkg/utils/database"
|
"photography-backend/pkg/utils/database"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
UserModel model.UserModel
|
UserModel model.UserModel
|
||||||
PhotoModel model.PhotoModel
|
PhotoModel model.PhotoModel
|
||||||
CategoryModel model.CategoryModel
|
CategoryModel model.CategoryModel
|
||||||
@ -29,8 +29,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
sqlxConn := sqlx.NewSqlConn(getSQLDriverName(c.Database.Driver), getSQLDataSource(c.Database))
|
sqlxConn := sqlx.NewSqlConn(getSQLDriverName(c.Database.Driver), getSQLDataSource(c.Database))
|
||||||
|
|
||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
DB: db,
|
DB: db,
|
||||||
UserModel: model.NewUserModel(sqlxConn),
|
UserModel: model.NewUserModel(sqlxConn),
|
||||||
PhotoModel: model.NewPhotoModel(sqlxConn),
|
PhotoModel: model.NewPhotoModel(sqlxConn),
|
||||||
CategoryModel: model.NewCategoryModel(sqlxConn),
|
CategoryModel: model.NewCategoryModel(sqlxConn),
|
||||||
|
|||||||
@ -7,13 +7,13 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// 通用错误代码
|
// 通用错误代码
|
||||||
Success = 0
|
Success = 0
|
||||||
ServerError = 500
|
ServerError = 500
|
||||||
ParamError = 400
|
ParamError = 400
|
||||||
AuthError = 401
|
AuthError = 401
|
||||||
NotFound = 404
|
NotFound = 404
|
||||||
Forbidden = 403
|
Forbidden = 403
|
||||||
InvalidParameter = 400 // 与 ParamError 统一
|
InvalidParameter = 400 // 与 ParamError 统一
|
||||||
|
|
||||||
// 业务错误代码
|
// 业务错误代码
|
||||||
UserNotFound = 1001
|
UserNotFound = 1001
|
||||||
@ -31,12 +31,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var codeText = map[int]string{
|
var codeText = map[int]string{
|
||||||
Success: "Success",
|
Success: "Success",
|
||||||
ServerError: "Server Error",
|
ServerError: "Server Error",
|
||||||
ParamError: "Parameter Error", // ParamError 和 InvalidParameter 都映射到这里
|
ParamError: "Parameter Error", // ParamError 和 InvalidParameter 都映射到这里
|
||||||
AuthError: "Authentication Error",
|
AuthError: "Authentication Error",
|
||||||
NotFound: "Not Found",
|
NotFound: "Not Found",
|
||||||
Forbidden: "Forbidden",
|
Forbidden: "Forbidden",
|
||||||
|
|
||||||
UserNotFound: "User Not Found",
|
UserNotFound: "User Not Found",
|
||||||
UserExists: "User Already Exists",
|
UserExists: "User Already Exists",
|
||||||
@ -83,7 +83,7 @@ func GetHttpStatus(code int) int {
|
|||||||
switch code {
|
switch code {
|
||||||
case Success:
|
case Success:
|
||||||
return http.StatusOK
|
return http.StatusOK
|
||||||
case ParamError: // ParamError 和 InvalidParameter 都是 400,所以只需要一个 case
|
case ParamError: // ParamError 和 InvalidParameter 都是 400,所以只需要一个 case
|
||||||
return http.StatusBadRequest
|
return http.StatusBadRequest
|
||||||
case AuthError, TokenExpired, TokenInvalid:
|
case AuthError, TokenExpired, TokenInvalid:
|
||||||
return http.StatusUnauthorized
|
return http.StatusUnauthorized
|
||||||
|
|||||||
@ -21,10 +21,10 @@ type Migration struct {
|
|||||||
|
|
||||||
// MigrationRecord 数据库中的迁移记录
|
// MigrationRecord 数据库中的迁移记录
|
||||||
type MigrationRecord struct {
|
type MigrationRecord struct {
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey"`
|
||||||
Version string `gorm:"uniqueIndex;size:255;not null"`
|
Version string `gorm:"uniqueIndex;size:255;not null"`
|
||||||
Description string `gorm:"size:500"`
|
Description string `gorm:"size:500"`
|
||||||
Applied bool `gorm:"default:false"`
|
Applied bool `gorm:"default:false"`
|
||||||
AppliedAt time.Time
|
AppliedAt time.Time
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Driver string `json:"driver"` // mysql, postgres, sqlite
|
Driver string `json:"driver"` // mysql, postgres, sqlite
|
||||||
Host string `json:"host,optional"`
|
Host string `json:"host,optional"`
|
||||||
Port int `json:"port,optional"`
|
Port int `json:"port,optional"`
|
||||||
Username string `json:"username,optional"`
|
Username string `json:"username,optional"`
|
||||||
|
|||||||
@ -23,12 +23,12 @@ import (
|
|||||||
// IntegrationTestSuite 集成测试套件
|
// IntegrationTestSuite 集成测试套件
|
||||||
type IntegrationTestSuite struct {
|
type IntegrationTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
svcCtx *svc.ServiceContext
|
svcCtx *svc.ServiceContext
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
authToken string
|
authToken string
|
||||||
userID int64
|
userID int64
|
||||||
photoID int64
|
photoID int64
|
||||||
categoryID int64
|
categoryID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +247,7 @@ func (suite *IntegrationTestSuite) testCategoryManagement() {
|
|||||||
err := json.Unmarshal(resp.Body, &createResp)
|
err := json.Unmarshal(resp.Body, &createResp)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
newCategoryID := createResp.Data.ID
|
newCategoryID := createResp.Data.Id
|
||||||
|
|
||||||
// 测试获取分类列表
|
// 测试获取分类列表
|
||||||
resp = suite.makeRequest("GET", "/api/v1/categories", nil, suite.authToken)
|
resp = suite.makeRequest("GET", "/api/v1/categories", nil, suite.authToken)
|
||||||
@ -257,7 +257,7 @@ func (suite *IntegrationTestSuite) testCategoryManagement() {
|
|||||||
err = json.Unmarshal(resp.Body, &listResp)
|
err = json.Unmarshal(resp.Body, &listResp)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
suite.GreaterOrEqual(len(listResp.Data), 2)
|
suite.GreaterOrEqual(len(listResp.Data.Categories), 2)
|
||||||
|
|
||||||
// 测试更新分类
|
// 测试更新分类
|
||||||
updateData := map[string]interface{}{
|
updateData := map[string]interface{}{
|
||||||
@ -276,7 +276,7 @@ func (suite *IntegrationTestSuite) testCategoryManagement() {
|
|||||||
// testPhotoManagement 照片管理测试
|
// testPhotoManagement 照片管理测试
|
||||||
func (suite *IntegrationTestSuite) testPhotoManagement() {
|
func (suite *IntegrationTestSuite) testPhotoManagement() {
|
||||||
// 测试创建照片记录(简化版,不包含实际文件上传)
|
// 测试创建照片记录(简化版,不包含实际文件上传)
|
||||||
photoData := map[string]interface{}{
|
_ = map[string]interface{}{
|
||||||
"title": "测试照片",
|
"title": "测试照片",
|
||||||
"description": "这是一个测试照片",
|
"description": "这是一个测试照片",
|
||||||
"file_path": "/uploads/test.jpg",
|
"file_path": "/uploads/test.jpg",
|
||||||
@ -306,7 +306,7 @@ func (suite *IntegrationTestSuite) testPhotoManagement() {
|
|||||||
err = json.Unmarshal(resp.Body, &listResp)
|
err = json.Unmarshal(resp.Body, &listResp)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
suite.GreaterOrEqual(len(listResp.Data), 1)
|
suite.GreaterOrEqual(len(listResp.Data.Photos), 1)
|
||||||
|
|
||||||
// 测试获取照片详情
|
// 测试获取照片详情
|
||||||
resp = suite.makeRequest("GET", fmt.Sprintf("/api/v1/photos/%d", suite.photoID), nil, suite.authToken)
|
resp = suite.makeRequest("GET", fmt.Sprintf("/api/v1/photos/%d", suite.photoID), nil, suite.authToken)
|
||||||
|
|||||||
@ -87,7 +87,7 @@ func (tc *TestContext) Login(t *testing.T) {
|
|||||||
err = json.Unmarshal(respBody, &resp)
|
err = json.Unmarshal(respBody, &resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tc.authToken = resp.Token
|
tc.authToken = resp.Data.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostJSON 发送 POST JSON 请求
|
// PostJSON 发送 POST JSON 请求
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { Badge } from './ui/badge'
|
import { Badge } from './ui/badge'
|
||||||
import { Button } from './ui/button'
|
import { Button } from './ui/button'
|
||||||
import { Alert, AlertDescription } from './ui/alert'
|
import { Alert, AlertDescription } from './ui/alert'
|
||||||
@ -21,7 +21,7 @@ export function ApiStatus() {
|
|||||||
}
|
}
|
||||||
}, [useRealApi])
|
}, [useRealApi])
|
||||||
|
|
||||||
const checkApiStatus = async () => {
|
const checkApiStatus = useCallback(async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
if (useRealApi) {
|
if (useRealApi) {
|
||||||
@ -38,14 +38,14 @@ export function ApiStatus() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}, [useRealApi])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkApiStatus()
|
checkApiStatus()
|
||||||
// 每30秒检查一次API状态
|
// 每30秒检查一次API状态
|
||||||
const interval = setInterval(checkApiStatus, 30000)
|
const interval = setInterval(checkApiStatus, 30000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [useRealApi])
|
}, [useRealApi, checkApiStatus])
|
||||||
|
|
||||||
const toggleApiMode = () => {
|
const toggleApiMode = () => {
|
||||||
const newMode = !useRealApi
|
const newMode = !useRealApi
|
||||||
|
|||||||
@ -33,7 +33,7 @@ interface CategoryPageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CategoryPage({ photos, onCategorySelect, onPhotosView }: CategoryPageProps) {
|
export function CategoryPage({ photos, onCategorySelect, onPhotosView }: CategoryPageProps) {
|
||||||
const { data: dynamicCategories = [] } = useCategories()
|
const { data: _dynamicCategories = [] } = useCategories()
|
||||||
const [searchQuery, setSearchQuery] = useState("")
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ export function CategoryPage({ photos, onCategorySelect, onPhotosView }: Categor
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex -space-x-2">
|
<div className="flex -space-x-2">
|
||||||
{getCategoryPreviewImages(category.photos).slice(0, 3).map((photo, idx) => (
|
{getCategoryPreviewImages(category.photos).slice(0, 3).map((photo, _idx) => (
|
||||||
<div
|
<div
|
||||||
key={photo.id}
|
key={photo.id}
|
||||||
className="w-12 h-12 rounded-lg border-2 border-white overflow-hidden"
|
className="w-12 h-12 rounded-lg border-2 border-white overflow-hidden"
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export function FilterBar({
|
|||||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||||
|
|
||||||
// 静态分类作为备选
|
// 静态分类作为备选
|
||||||
const staticCategories = [
|
const _staticCategories = [
|
||||||
{ id: "all", name: "全部作品" },
|
{ id: "all", name: "全部作品" },
|
||||||
{ id: "urban", name: "城市风光" },
|
{ id: "urban", name: "城市风光" },
|
||||||
{ id: "nature", name: "自然风景" },
|
{ id: "nature", name: "自然风景" },
|
||||||
@ -191,7 +191,7 @@ export function FilterBar({
|
|||||||
|
|
||||||
{searchText.trim() && (
|
{searchText.trim() && (
|
||||||
<Badge variant="outline" className="gap-1">
|
<Badge variant="outline" className="gap-1">
|
||||||
搜索: "{searchText.trim()}"
|
搜索: “{searchText.trim()}”
|
||||||
<X
|
<X
|
||||||
className="h-3 w-3 cursor-pointer"
|
className="h-3 w-3 cursor-pointer"
|
||||||
onClick={handleClearSearch}
|
onClick={handleClearSearch}
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Hash,
|
Hash,
|
||||||
Filter,
|
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Camera,
|
Camera,
|
||||||
Sparkles
|
Sparkles
|
||||||
@ -95,7 +94,7 @@ export function TagCloud({ photos, onTagSelect, onPhotosView }: TagCloudProps) {
|
|||||||
|
|
||||||
// 过滤和排序标签
|
// 过滤和排序标签
|
||||||
const filteredTags = useMemo(() => {
|
const filteredTags = useMemo(() => {
|
||||||
let filtered = tagStats.filter(tag =>
|
const filtered = tagStats.filter(tag =>
|
||||||
tag.count >= minCount &&
|
tag.count >= minCount &&
|
||||||
(searchQuery.trim() === '' || tag.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
(searchQuery.trim() === '' || tag.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||||
)
|
)
|
||||||
@ -237,7 +236,7 @@ export function TagCloud({ photos, onTagSelect, onPhotosView }: TagCloudProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as any)}
|
onChange={(e) => setSortBy(e.target.value as 'popularity' | 'alphabetical' | 'recent')}
|
||||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
|
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||||
>
|
>
|
||||||
<option value="popularity">按热度</option>
|
<option value="popularity">按热度</option>
|
||||||
@ -324,7 +323,7 @@ export function TagCloud({ photos, onTagSelect, onPhotosView }: TagCloudProps) {
|
|||||||
|
|
||||||
{/* 最近照片预览 */}
|
{/* 最近照片预览 */}
|
||||||
<div className="flex -space-x-2 mt-3">
|
<div className="flex -space-x-2 mt-3">
|
||||||
{tag.recentPhotos.slice(0, 3).map((photo, idx) => (
|
{tag.recentPhotos.slice(0, 3).map((photo, _idx) => (
|
||||||
<div
|
<div
|
||||||
key={photo.id}
|
key={photo.id}
|
||||||
className="w-8 h-8 rounded-full border-2 border-white overflow-hidden"
|
className="w-8 h-8 rounded-full border-2 border-white overflow-hidden"
|
||||||
|
|||||||
@ -15,9 +15,12 @@ const api = axios.create({
|
|||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
// 可以在这里添加token等认证信息
|
// 可以在这里添加token等认证信息
|
||||||
const token = localStorage.getItem('token')
|
// 检查是否在浏览器环境中
|
||||||
if (token) {
|
if (typeof window !== 'undefined') {
|
||||||
config.headers.Authorization = `Bearer ${token}`
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
@ -42,9 +45,11 @@ api.interceptors.response.use(
|
|||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// 处理未授权
|
// 处理未授权 - 仅在浏览器环境中执行
|
||||||
localStorage.removeItem('token')
|
if (typeof window !== 'undefined') {
|
||||||
window.location.href = '/login'
|
localStorage.removeItem('token')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class CategoryService {
|
|||||||
// 获取所有分类
|
// 获取所有分类
|
||||||
async getAllCategories(): Promise<Category[]> {
|
async getAllCategories(): Promise<Category[]> {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
const response: any = await api.get('/categories?page=1&page_size=100')
|
const response: { categories: Category[] } = await api.get('/categories?page=1&page_size=100')
|
||||||
return response?.categories || []
|
return response?.categories || []
|
||||||
} else {
|
} else {
|
||||||
// Mock API 返回字符串数组,需要转换
|
// Mock API 返回字符串数组,需要转换
|
||||||
|
|||||||
@ -58,27 +58,54 @@ export const queryKeys = {
|
|||||||
categories: ['categories'] as const,
|
categories: ['categories'] as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 后端照片数据结构
|
||||||
|
interface BackendPhoto {
|
||||||
|
id: number
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
src?: string
|
||||||
|
url?: string
|
||||||
|
image_path?: string
|
||||||
|
file_path?: string
|
||||||
|
thumbnail_path?: string
|
||||||
|
category?: string
|
||||||
|
category_id?: number
|
||||||
|
user_id?: number
|
||||||
|
tags?: string[]
|
||||||
|
date?: string
|
||||||
|
created_at?: number
|
||||||
|
updated_at?: number
|
||||||
|
exif?: {
|
||||||
|
camera?: string
|
||||||
|
lens?: string
|
||||||
|
settings?: string
|
||||||
|
location?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 数据转换工具
|
// 数据转换工具
|
||||||
const transformPhoto = async (backendPhoto: any): Promise<Photo> => {
|
const transformPhoto = async (backendPhoto: BackendPhoto): Promise<Photo> => {
|
||||||
// 如果使用Mock API,直接返回
|
// 如果使用Mock API,直接返回
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API !== 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API !== 'true') {
|
||||||
return {
|
return {
|
||||||
...backendPhoto,
|
id: backendPhoto.id,
|
||||||
|
title: backendPhoto.title || '无标题',
|
||||||
|
description: backendPhoto.description || '',
|
||||||
src: backendPhoto.src || '/placeholder.jpg',
|
src: backendPhoto.src || '/placeholder.jpg',
|
||||||
category: backendPhoto.category || 'general',
|
category: backendPhoto.category || 'general',
|
||||||
tags: backendPhoto.tags || [],
|
tags: backendPhoto.tags || [],
|
||||||
date: backendPhoto.date || new Date().toISOString().split('T')[0],
|
date: backendPhoto.date || new Date().toISOString().split('T')[0],
|
||||||
exif: backendPhoto.exif || {
|
exif: {
|
||||||
camera: '未知',
|
camera: backendPhoto.exif?.camera || '未知',
|
||||||
lens: '未知',
|
lens: backendPhoto.exif?.lens || '未知',
|
||||||
settings: '未知',
|
settings: backendPhoto.exif?.settings || '未知',
|
||||||
location: '未知'
|
location: backendPhoto.exif?.location || '未知'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分类名称
|
// 获取分类名称
|
||||||
const categoryName = await categoryService.getCategoryName(backendPhoto.category_id)
|
const categoryName = await categoryService.getCategoryName(backendPhoto.category_id || 1)
|
||||||
|
|
||||||
// 转换后端API数据格式
|
// 转换后端API数据格式
|
||||||
return {
|
return {
|
||||||
@ -88,7 +115,7 @@ const transformPhoto = async (backendPhoto: any): Promise<Photo> => {
|
|||||||
src: backendPhoto.file_path ? `http://localhost:8080${backendPhoto.file_path}` : '/placeholder.jpg',
|
src: backendPhoto.file_path ? `http://localhost:8080${backendPhoto.file_path}` : '/placeholder.jpg',
|
||||||
category: categoryName,
|
category: categoryName,
|
||||||
tags: [], // 后端暂无标签系统,使用空数组
|
tags: [], // 后端暂无标签系统,使用空数组
|
||||||
date: new Date(backendPhoto.created_at * 1000).toISOString().split('T')[0],
|
date: new Date((backendPhoto.created_at || Date.now() / 1000) * 1000).toISOString().split('T')[0],
|
||||||
exif: {
|
exif: {
|
||||||
camera: '未知',
|
camera: '未知',
|
||||||
lens: '未知',
|
lens: '未知',
|
||||||
@ -105,11 +132,11 @@ const transformPhoto = async (backendPhoto: any): Promise<Photo> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformCategory = (backendCategory: any): string => {
|
const _transformCategory = (backendCategory: Category | string): string => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API !== 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API !== 'true') {
|
||||||
return backendCategory
|
return typeof backendCategory === 'string' ? backendCategory : backendCategory.name
|
||||||
}
|
}
|
||||||
return backendCategory.name
|
return typeof backendCategory === 'string' ? backendCategory : backendCategory.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有照片
|
// 获取所有照片
|
||||||
@ -119,13 +146,13 @@ export const usePhotos = () => {
|
|||||||
queryFn: async (): Promise<Photo[]> => {
|
queryFn: async (): Promise<Photo[]> => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
// 使用真实API,带分页参数
|
// 使用真实API,带分页参数
|
||||||
const response: any = await api.get('/photos?page=1&page_size=100')
|
const response: { photos: BackendPhoto[] } = await api.get('/photos?page=1&page_size=100')
|
||||||
const photos = response?.photos || []
|
const photos = response?.photos || []
|
||||||
// 并发处理所有照片的转换
|
// 并发处理所有照片的转换
|
||||||
return Promise.all(photos.map(transformPhoto))
|
return Promise.all(photos.map(transformPhoto))
|
||||||
} else {
|
} else {
|
||||||
// 使用Mock API
|
// 使用Mock API
|
||||||
const photos: any[] = await api.get('/photos')
|
const photos: BackendPhoto[] = await api.get('/photos')
|
||||||
return Promise.all(photos.map(transformPhoto))
|
return Promise.all(photos.map(transformPhoto))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -139,7 +166,7 @@ export const usePhotosPaginated = (page: number = 1, pageSize: number = 12) => {
|
|||||||
queryKey: [...queryKeys.photos, 'paginated', page, pageSize],
|
queryKey: [...queryKeys.photos, 'paginated', page, pageSize],
|
||||||
queryFn: async (): Promise<{ photos: Photo[], total: number, hasMore: boolean }> => {
|
queryFn: async (): Promise<{ photos: Photo[], total: number, hasMore: boolean }> => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
const response: any = await api.get(`/photos?page=${page}&page_size=${pageSize}`)
|
const response: { photos: BackendPhoto[], total: number } = await api.get(`/photos?page=${page}&page_size=${pageSize}`)
|
||||||
const photos = response?.photos || []
|
const photos = response?.photos || []
|
||||||
const total = response?.total || 0
|
const total = response?.total || 0
|
||||||
const transformedPhotos = await Promise.all(photos.map(transformPhoto))
|
const transformedPhotos = await Promise.all(photos.map(transformPhoto))
|
||||||
@ -150,7 +177,7 @@ export const usePhotosPaginated = (page: number = 1, pageSize: number = 12) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 使用Mock API - 模拟分页
|
// 使用Mock API - 模拟分页
|
||||||
const allPhotos: any[] = await api.get('/photos')
|
const allPhotos: BackendPhoto[] = await api.get('/photos')
|
||||||
const startIndex = (page - 1) * pageSize
|
const startIndex = (page - 1) * pageSize
|
||||||
const endIndex = startIndex + pageSize
|
const endIndex = startIndex + pageSize
|
||||||
const paginatedPhotos = allPhotos.slice(startIndex, endIndex)
|
const paginatedPhotos = allPhotos.slice(startIndex, endIndex)
|
||||||
@ -167,17 +194,17 @@ export const usePhotosPaginated = (page: number = 1, pageSize: number = 12) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 无限滚动照片查询
|
// 无限滚动照片查询
|
||||||
export const useInfinitePhotos = (pageSize: number = 12) => {
|
export const useInfinitePhotos = (_pageSize: number = 12) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [...queryKeys.photos, 'infinite'],
|
queryKey: [...queryKeys.photos, 'infinite'],
|
||||||
queryFn: async (): Promise<Photo[]> => {
|
queryFn: async (): Promise<Photo[]> => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
// 获取所有照片用于前端分页
|
// 获取所有照片用于前端分页
|
||||||
const response: any = await api.get('/photos?page=1&page_size=200')
|
const response: { photos: BackendPhoto[] } = await api.get('/photos?page=1&page_size=200')
|
||||||
const photos = response?.photos || []
|
const photos = response?.photos || []
|
||||||
return Promise.all(photos.map(transformPhoto))
|
return Promise.all(photos.map(transformPhoto))
|
||||||
} else {
|
} else {
|
||||||
const photos: any[] = await api.get('/photos')
|
const photos: BackendPhoto[] = await api.get('/photos')
|
||||||
return Promise.all(photos.map(transformPhoto))
|
return Promise.all(photos.map(transformPhoto))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -191,7 +218,7 @@ export const usePhoto = (id: number) => {
|
|||||||
queryKey: queryKeys.photo(id),
|
queryKey: queryKeys.photo(id),
|
||||||
queryFn: async (): Promise<Photo> => {
|
queryFn: async (): Promise<Photo> => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
const response = await api.get(`/photos/${id}`)
|
const response: BackendPhoto = await api.get(`/photos/${id}`)
|
||||||
return await transformPhoto(response)
|
return await transformPhoto(response)
|
||||||
} else {
|
} else {
|
||||||
return api.get(`/photos/${id}`)
|
return api.get(`/photos/${id}`)
|
||||||
@ -207,7 +234,7 @@ export const useCategories = () => {
|
|||||||
queryKey: queryKeys.categories,
|
queryKey: queryKeys.categories,
|
||||||
queryFn: async (): Promise<string[]> => {
|
queryFn: async (): Promise<string[]> => {
|
||||||
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
if (process.env.NEXT_PUBLIC_USE_REAL_API === 'true') {
|
||||||
const response: any = await api.get('/categories?page=1&page_size=100')
|
const response: { categories: Category[] } = await api.get('/categories?page=1&page_size=100')
|
||||||
const categories = response?.categories || []
|
const categories = response?.categories || []
|
||||||
return categories.map((cat: Category) => cat.name)
|
return categories.map((cat: Category) => cat.name)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user