diff --git a/apps/web/src/api/auth.ts b/apps/web/src/api/auth.ts deleted file mode 100644 index da6e6340f..000000000 --- a/apps/web/src/api/auth.ts +++ /dev/null @@ -1,188 +0,0 @@ -/** - * 认证相关 API - */ -import axios from "axios" -import apiClient from "./client" - -// 类型定义 -export interface LoginRequest { - email: string - password: string -} - -export interface LoginResponse { - access_token: string - refresh_token?: string | null - token_type: string - expires_in: number - user_id: string - email: string - username: string - display_name: string -} - -export interface RegisterRequest { - email: string - password: string - username: string - display_name?: string -} - -export interface User { - id: string - user_id: string - email: string - username: string - display_name: string - is_email_verified: boolean - email_verified: boolean - created_at?: string -} - -export interface UserResponse { - id?: string - user_id?: string - email: string - username: string - display_name: string - is_email_verified?: boolean - email_verified?: boolean - created_at?: string -} - -export const normalizeUser = (data: UserResponse): User => { - const userId = data.id ?? data.user_id ?? "" - const emailVerified = data.is_email_verified ?? data.email_verified ?? false - - return { - id: userId, - user_id: userId, - email: data.email, - username: data.username, - display_name: data.display_name, - is_email_verified: emailVerified, - email_verified: emailVerified, - created_at: data.created_at, - } -} - -// 登录 -export const login = async (data: LoginRequest): Promise => { - const response = await apiClient.post("/auth/login", data) - return response.data -} - -// 刷新 access_token(使用裸 axios 避免拦截器递归) -export const refreshAccessToken = async (refreshToken: string): Promise => { - const baseURL = apiClient.defaults.baseURL ?? "" - const response = await axios.post(`${baseURL}/auth/refresh`, { - refresh_token: refreshToken, - }) - return response.data -} - -// 注册 -export const register = async (data: RegisterRequest): Promise<{ message: string }> => { - const response = await apiClient.post("/auth/register", data) - return response.data -} - -// 登出 -export const logout = async (): Promise => { - await apiClient.post("/auth/logout") -} - -// 获取当前用户 -export const getCurrentUser = async (): Promise => { - const response = await apiClient.get("/auth/me") - return normalizeUser(response.data) -} - -// 请求密码重置 -export const requestPasswordReset = async (email: string): Promise<{ message: string }> => { - const response = await apiClient.post("/auth/forgot-password", { email }) - return response.data -} - -// 重置密码 -export const resetPassword = async ( - token: string, - newPassword: string, -): Promise<{ message: string }> => { - const response = await apiClient.post("/auth/reset-password", { - token, - new_password: newPassword, - }) - return response.data -} - -// 验证邮箱 -export const verifyEmail = async (token: string): Promise<{ message: string }> => { - const response = await apiClient.post("/auth/verify-email", { token }) - return response.data -} - -/* ========== 微信登录 ========== */ - -export interface WechatAuthUrlResponse { - auth_url: string - state: string -} - -export interface WechatCallbackResponse { - access_token: string - refresh_token?: string | null - user_id: string - display_name: string - avatar_url: string - is_new_user: boolean - binding_complete: boolean - expires_in: number -} - -export interface SendVerificationCodeRequest { - target: "email" | "phone" - value: string - purpose: "bind" | "login" | "reset_password" -} - -export interface BindContactRequest { - email?: string - email_code?: string - phone?: string - phone_code?: string -} - -export interface BindContactResponse { - success: boolean - user: User -} - -// 获取微信授权链接 -export const getWechatAuthUrl = async (): Promise => { - const response = await apiClient.get("/auth/wechat/url") - return response.data -} - -// 微信回调登录 -export const wechatCallback = async ( - code: string, - state: string, -): Promise => { - const response = await apiClient.post("/auth/wechat/callback", { code, state }) - return response.data -} - -// 发送验证码 -export const sendVerificationCode = async ( - data: SendVerificationCodeRequest, -): Promise<{ message: string }> => { - const response = await apiClient.post("/auth/send-verification-code", data) - return response.data -} - -// 绑定联系方式 -export const bindContact = async (data: BindContactRequest): Promise => { - const response = await apiClient.post("/auth/bind-contact", data) - return response.data -} diff --git a/apps/web/src/api/auth/contact.ts b/apps/web/src/api/auth/contact.ts new file mode 100644 index 000000000..feb846965 --- /dev/null +++ b/apps/web/src/api/auth/contact.ts @@ -0,0 +1,20 @@ +import apiClient from "../client" +import type { SendVerificationCodeRequest, BindContactRequest, BindContactResponse } from "./types" + +/** + * 发送验证码 + */ +export const sendVerificationCode = async ( + data: SendVerificationCodeRequest, +): Promise<{ message: string }> => { + const response = await apiClient.post("/auth/send-verification-code", data) + return response.data +} + +/** + * 绑定联系方式 + */ +export const bindContact = async (data: BindContactRequest): Promise => { + const response = await apiClient.post("/auth/bind-contact", data) + return response.data +} diff --git a/apps/web/src/api/auth/currentUser.ts b/apps/web/src/api/auth/currentUser.ts new file mode 100644 index 000000000..a31922de1 --- /dev/null +++ b/apps/web/src/api/auth/currentUser.ts @@ -0,0 +1,11 @@ +import apiClient from "../client" +import type { User, UserResponse } from "./types" +import { normalizeUser } from "./user" + +/** + * 获取当前用户 + */ +export const getCurrentUser = async (): Promise => { + const response = await apiClient.get("/auth/me") + return normalizeUser(response.data) +} diff --git a/apps/web/src/api/auth/email.ts b/apps/web/src/api/auth/email.ts new file mode 100644 index 000000000..ade0be2d2 --- /dev/null +++ b/apps/web/src/api/auth/email.ts @@ -0,0 +1,9 @@ +import apiClient from "../client" + +/** + * 验证邮箱 + */ +export const verifyEmail = async (token: string): Promise<{ message: string }> => { + const response = await apiClient.post("/auth/verify-email", { token }) + return response.data +} diff --git a/apps/web/src/api/auth/index.ts b/apps/web/src/api/auth/index.ts new file mode 100644 index 000000000..966c92c44 --- /dev/null +++ b/apps/web/src/api/auth/index.ts @@ -0,0 +1,39 @@ +/** + * 认证相关 API + * 保持向后兼容,从子模块 re-export + */ + +// 类型 +export type { + LoginRequest, + LoginResponse, + RegisterRequest, + User, + UserResponse, + WechatAuthUrlResponse, + WechatCallbackResponse, + SendVerificationCodeRequest, + BindContactRequest, + BindContactResponse, +} from "./types" + +// 用户工具函数 +export { normalizeUser } from "./user" + +// 登录/注册/登出/刷新 +export { login, refreshAccessToken, register, logout } from "./login" + +// 当前用户 +export { getCurrentUser } from "./currentUser" + +// 密码重置 +export { requestPasswordReset, resetPassword } from "./password" + +// 邮箱验证 +export { verifyEmail } from "./email" + +// 微信登录 +export { getWechatAuthUrl, wechatCallback } from "./wechat" + +// 联系方式 +export { sendVerificationCode, bindContact } from "./contact" diff --git a/apps/web/src/api/auth/login.ts b/apps/web/src/api/auth/login.ts new file mode 100644 index 000000000..09887a1b8 --- /dev/null +++ b/apps/web/src/api/auth/login.ts @@ -0,0 +1,37 @@ +import axios from "axios" +import apiClient from "../client" +import type { LoginRequest, LoginResponse, RegisterRequest } from "./types" + +/** + * 登录 + */ +export const login = async (data: LoginRequest): Promise => { + const response = await apiClient.post("/auth/login", data) + return response.data +} + +/** + * 刷新 access_token(使用裸 axios 避免拦截器递归) + */ +export const refreshAccessToken = async (refreshToken: string): Promise => { + const baseURL = apiClient.defaults.baseURL ?? "" + const response = await axios.post(`${baseURL}/auth/refresh`, { + refresh_token: refreshToken, + }) + return response.data +} + +/** + * 注册 + */ +export const register = async (data: RegisterRequest): Promise<{ message: string }> => { + const response = await apiClient.post("/auth/register", data) + return response.data +} + +/** + * 登出 + */ +export const logout = async (): Promise => { + await apiClient.post("/auth/logout") +} diff --git a/apps/web/src/api/auth/password.ts b/apps/web/src/api/auth/password.ts new file mode 100644 index 000000000..4791d129c --- /dev/null +++ b/apps/web/src/api/auth/password.ts @@ -0,0 +1,23 @@ +import apiClient from "../client" + +/** + * 请求密码重置 + */ +export const requestPasswordReset = async (email: string): Promise<{ message: string }> => { + const response = await apiClient.post("/auth/forgot-password", { email }) + return response.data +} + +/** + * 重置密码 + */ +export const resetPassword = async ( + token: string, + newPassword: string, +): Promise<{ message: string }> => { + const response = await apiClient.post("/auth/reset-password", { + token, + new_password: newPassword, + }) + return response.data +} diff --git a/apps/web/src/api/auth/types.ts b/apps/web/src/api/auth/types.ts new file mode 100644 index 000000000..023e0fa32 --- /dev/null +++ b/apps/web/src/api/auth/types.ts @@ -0,0 +1,82 @@ +/** + * 认证相关类型定义 + */ + +export interface LoginRequest { + email: string + password: string +} + +export interface LoginResponse { + access_token: string + refresh_token?: string | null + token_type: string + expires_in: number + user_id: string + email: string + username: string + display_name: string +} + +export interface RegisterRequest { + email: string + password: string + username: string + display_name?: string +} + +export interface User { + id: string + user_id: string + email: string + username: string + display_name: string + is_email_verified: boolean + email_verified: boolean + created_at?: string +} + +export interface UserResponse { + id?: string + user_id?: string + email: string + username: string + display_name: string + is_email_verified?: boolean + email_verified?: boolean + created_at?: string +} + +export interface WechatAuthUrlResponse { + auth_url: string + state: string +} + +export interface WechatCallbackResponse { + access_token: string + refresh_token?: string | null + user_id: string + display_name: string + avatar_url: string + is_new_user: boolean + binding_complete: boolean + expires_in: number +} + +export interface SendVerificationCodeRequest { + target: "email" | "phone" + value: string + purpose: "bind" | "login" | "reset_password" +} + +export interface BindContactRequest { + email?: string + email_code?: string + phone?: string + phone_code?: string +} + +export interface BindContactResponse { + success: boolean + user: User +} diff --git a/apps/web/src/api/auth/user.ts b/apps/web/src/api/auth/user.ts new file mode 100644 index 000000000..5c345e3b5 --- /dev/null +++ b/apps/web/src/api/auth/user.ts @@ -0,0 +1,20 @@ +import type { User, UserResponse } from "./types" + +/** + * 规范化用户数据,兼容不同后端返回格式 + */ +export const normalizeUser = (data: UserResponse): User => { + const userId = data.id ?? data.user_id ?? "" + const emailVerified = data.is_email_verified ?? data.email_verified ?? false + + return { + id: userId, + user_id: userId, + email: data.email, + username: data.username, + display_name: data.display_name, + is_email_verified: emailVerified, + email_verified: emailVerified, + created_at: data.created_at, + } +} diff --git a/apps/web/src/api/auth/wechat.ts b/apps/web/src/api/auth/wechat.ts new file mode 100644 index 000000000..1e41a8844 --- /dev/null +++ b/apps/web/src/api/auth/wechat.ts @@ -0,0 +1,21 @@ +import apiClient from "../client" +import type { WechatAuthUrlResponse, WechatCallbackResponse } from "./types" + +/** + * 获取微信授权链接 + */ +export const getWechatAuthUrl = async (): Promise => { + const response = await apiClient.get("/auth/wechat/url") + return response.data +} + +/** + * 微信回调登录 + */ +export const wechatCallback = async ( + code: string, + state: string, +): Promise => { + const response = await apiClient.post("/auth/wechat/callback", { code, state }) + return response.data +} diff --git a/apps/web/src/test/api/auth.test.ts b/apps/web/src/test/api/auth.test.ts index 86b0a0116..4331a130a 100644 --- a/apps/web/src/test/api/auth.test.ts +++ b/apps/web/src/test/api/auth.test.ts @@ -1,3 +1,7 @@ +/** + * Auth API 测试 + * 对应 api/auth/ 目录化后的模块 + */ import { describe, expect, it, vi, beforeEach } from "vitest" import { normalizeUser, @@ -10,7 +14,6 @@ import { resetPassword, verifyEmail, } from "@/api/auth" - const mockPost = vi.fn() const mockGet = vi.fn() const mockAxiosPost = vi.fn()