Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
CLAUDE_API_KEY=YOUR_CLAUDE_API_KEY
REDIRECT_URL=https://localhost:3458/auth/callback
SHOPIFY_API_KEY=YOUR_APP_CLIENT_ID
SHOPIFY_API_SECRET=YOUR_APP_SECRET_KEY

# Shopify OAuth Scopes - 必须包含你需要的所有权限
# 更改此配置后,需要重新安装App才能生效
SCOPES=read_orders,read_products,read_customers,read_inventory,write_products
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ shopify.*.toml

# Hide files auto-generated by react router
.react-router/

.idea
164 changes: 164 additions & 0 deletions app/db.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,167 @@ export async function getCustomerAccountUrls(conversationId) {
return null;
}
}

// ============================================
// ChatUser 用户管理相关函数
// ============================================

/**
* 创建新的聊天用户
* @param {string} shopId - 商店ID
* @param {string} username - 用户名
* @param {string} passwordHash - 密码哈希
* @returns {Promise<Object>} - 创建的用户
*/
export async function createChatUser(shopId, username, passwordHash) {
try {
return await prisma.chatUser.create({
data: {
shopId,
username,
passwordHash
}
});
} catch (error) {
console.error('Error creating chat user:', error);
throw error;
}
}

/**
* 根据商店ID和用户名查找用户
* @param {string} shopId - 商店ID
* @param {string} username - 用户名
* @returns {Promise<Object|null>} - 用户对象或null
*/
export async function getChatUserByUsername(shopId, username) {
try {
return await prisma.chatUser.findUnique({
where: {
shopId_username: { shopId, username }
}
});
} catch (error) {
console.error('Error getting chat user:', error);
return null;
}
}

/**
* 根据ID查找用户
* @param {string} userId - 用户ID
* @returns {Promise<Object|null>} - 用户对象或null
*/
export async function getChatUserById(userId) {
try {
return await prisma.chatUser.findUnique({
where: { id: userId }
});
} catch (error) {
console.error('Error getting chat user by id:', error);
return null;
}
}

/**
* 更新用户的当前提示词
* @param {string} userId - 用户ID
* @param {string} prompt - 新的提示词
* @returns {Promise<Object>} - 更新后的用户对象
*/
export async function updateUserPrompt(userId, prompt) {
try {
// 获取当前用户以检查是否需要保存历史
const user = await prisma.chatUser.findUnique({
where: { id: userId }
});

// 如果用户有旧的提示词,保存到历史记录
if (user && user.currentPrompt) {
// 获取当前最大版本号
const lastHistory = await prisma.promptHistory.findFirst({
where: { userId },
orderBy: { version: 'desc' }
});
const newVersion = (lastHistory?.version || 0) + 1;

// 保存旧提示词到历史
await prisma.promptHistory.create({
data: {
userId,
content: user.currentPrompt,
version: newVersion
}
});
}

// 更新当前提示词
return await prisma.chatUser.update({
where: { id: userId },
data: {
currentPrompt: prompt,
updatedAt: new Date()
}
});
} catch (error) {
console.error('Error updating user prompt:', error);
throw error;
}
}

/**
* 获取用户的提示词历史记录
* @param {string} userId - 用户ID
* @returns {Promise<Array>} - 提示词历史列表
*/
export async function getPromptHistory(userId) {
try {
return await prisma.promptHistory.findMany({
where: { userId },
orderBy: { version: 'desc' }
});
} catch (error) {
console.error('Error getting prompt history:', error);
return [];
}
}

/**
* 根据ID获取某个历史提示词
* @param {string} historyId - 历史记录ID
* @returns {Promise<Object|null>} - 历史记录或null
*/
export async function getPromptHistoryById(historyId) {
try {
return await prisma.promptHistory.findUnique({
where: { id: historyId }
});
} catch (error) {
console.error('Error getting prompt history by id:', error);
return null;
}
}

/**
* 恢复用户提示词到某个历史版本
* @param {string} userId - 用户ID
* @param {string} historyId - 历史记录ID
* @returns {Promise<Object>} - 更新后的用户对象
*/
export async function restorePromptFromHistory(userId, historyId) {
try {
const history = await prisma.promptHistory.findUnique({
where: { id: historyId }
});

if (!history || history.userId !== userId) {
throw new Error('History not found or unauthorized');
}

// 使用 updateUserPrompt 来保存当前提示词到历史并更新
return await updateUserPrompt(userId, history.content);
} catch (error) {
console.error('Error restoring prompt from history:', error);
throw error;
}
}
214 changes: 214 additions & 0 deletions app/routes/api.chat-auth.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/**
* Chat User Authentication API
* 用户注册/登录接口
*/
import crypto from 'crypto';
import { createChatUser, getChatUserByUsername, getChatUserById } from "../db.server";

/**
* 简单的密码哈希函数
*/
function hashPassword(password) {
return crypto.createHash('sha256').update(password).digest('hex');
}

/**
* 生成简单的认证 token
*/
function generateToken(userId) {
const payload = {
userId,
exp: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7天过期
};
return Buffer.from(JSON.stringify(payload)).toString('base64');
}

/**
* 解析认证 token
*/
function parseToken(token) {
try {
const payload = JSON.parse(Buffer.from(token, 'base64').toString('utf8'));
if (payload.exp < Date.now()) {
return null; // Token 已过期
}
return payload;
} catch {
return null;
}
}

/**
* GET 请求 - 检查登录状态
*/
export async function loader({ request }) {
// Handle OPTIONS requests (CORS preflight)
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: corsHeaders(request)
});
}

const url = new URL(request.url);
const action = url.searchParams.get("action");

if (action === "check") {
// 从 header 中获取 token
const authHeader = request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return jsonResponse({ authenticated: false }, 200, request);
}

const token = authHeader.substring(7);
const payload = parseToken(token);

if (!payload) {
return jsonResponse({ authenticated: false }, 200, request);
}

const user = await getChatUserById(payload.userId);
if (!user || !user.isActive) {
return jsonResponse({ authenticated: false }, 200, request);
}

return jsonResponse({
authenticated: true,
userId: user.id,
username: user.username,
currentPrompt: user.currentPrompt
}, 200, request);
}

return jsonResponse({ error: "Invalid action" }, 400, request);
}

/**
* POST 请求 - 注册/登录
*/
export async function action({ request }) {
// Handle OPTIONS requests (CORS preflight)
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: corsHeaders(request)
});
}

try {
const body = await request.json();
const { action, username, password, shopId } = body;

// 验证必要参数
if (!username || !password || !shopId) {
return jsonResponse({ error: "缺少必要参数" }, 400, request);
}

// 用户名验证
if (username.length < 2 || username.length > 50) {
return jsonResponse({ error: "用户名长度应为2-50个字符" }, 400, request);
}

// 密码验证
if (password.length < 4) {
return jsonResponse({ error: "密码长度至少4个字符" }, 400, request);
}

if (action === "register") {
return handleRegister(shopId, username, password, request);
} else if (action === "login") {
return handleLogin(shopId, username, password, request);
} else {
return jsonResponse({ error: "无效的操作" }, 400, request);
}
} catch (error) {
console.error("Auth error:", error);
return jsonResponse({ error: "服务器错误" }, 500, request);
}
}

/**
* 处理用户注册
*/
async function handleRegister(shopId, username, password, request) {
// 检查用户是否已存在
const existingUser = await getChatUserByUsername(shopId, username);
if (existingUser) {
return jsonResponse({ error: "用户名已存在" }, 409, request);
}

// 创建新用户
const passwordHash = hashPassword(password);
const user = await createChatUser(shopId, username, passwordHash);

// 生成 token
const token = generateToken(user.id);

return jsonResponse({
success: true,
userId: user.id,
username: user.username,
token
}, 201, request);
}

/**
* 处理用户登录
*/
async function handleLogin(shopId, username, password, request) {
// 查找用户
const user = await getChatUserByUsername(shopId, username);
if (!user) {
return jsonResponse({ error: "用户名或密码错误" }, 401, request);
}

// 验证密码
const passwordHash = hashPassword(password);
if (user.passwordHash !== passwordHash) {
return jsonResponse({ error: "用户名或密码错误" }, 401, request);
}

// 检查用户是否激活
if (!user.isActive) {
return jsonResponse({ error: "账户已被禁用" }, 403, request);
}

// 生成 token
const token = generateToken(user.id);

return jsonResponse({
success: true,
userId: user.id,
username: user.username,
currentPrompt: user.currentPrompt,
token
}, 200, request);
}

/**
* 返回 JSON 响应
*/
function jsonResponse(data, status, request) {
return new Response(JSON.stringify(data), {
status,
headers: {
"Content-Type": "application/json",
...corsHeaders(request)
}
});
}

/**
* CORS 头信息
*/
function corsHeaders(request) {
const origin = request.headers.get("Origin") || "*";

return {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Accept, Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Max-Age": "86400"
};
}
Loading