first commit
This commit is contained in:
214
apps/gateway/src/sync/routes.ts
Normal file
214
apps/gateway/src/sync/routes.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import type { Express, Request, Response, NextFunction } from "express";
|
||||
import { config } from "../config";
|
||||
import { logger } from "../logger";
|
||||
import { verifySyncToken } from "./crypto";
|
||||
import { SyncRepository } from "./repository";
|
||||
import {
|
||||
syncLoginBodySchema,
|
||||
syncRecordsPayloadSchema,
|
||||
syncSettingsPayloadSchema,
|
||||
syncServersPayloadSchema
|
||||
} from "./schema";
|
||||
import { loginMiniprogramUser } from "./userService";
|
||||
import { registerMiniprogramTtsRoutes } from "../tts/routes";
|
||||
|
||||
interface SyncAuthedRequest extends Request {
|
||||
syncUser?: {
|
||||
userId: string;
|
||||
openid: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ensureSyncEnabled(res: Response): boolean {
|
||||
if (config.sync.enabled) {
|
||||
return true;
|
||||
}
|
||||
logger.warn(
|
||||
{
|
||||
hasAppId: Boolean(config.sync.miniprogramAppId),
|
||||
hasAppSecret: Boolean(config.sync.miniprogramAppSecret),
|
||||
hasSecretCurrent: Boolean(config.sync.secretCurrent)
|
||||
},
|
||||
"小程序同步服务未启用"
|
||||
);
|
||||
res.status(503).json({
|
||||
ok: false,
|
||||
code: "SYNC_DISABLED",
|
||||
message: "小程序同步服务未配置"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function requireGatewayToken(req: Request, res: Response): boolean {
|
||||
const token = String(req.headers["x-gateway-token"] || "");
|
||||
if (token === config.gatewayToken) {
|
||||
return true;
|
||||
}
|
||||
logger.warn(
|
||||
{
|
||||
path: req.path,
|
||||
hasToken: Boolean(token)
|
||||
},
|
||||
"小程序同步登录缺少有效 gateway token"
|
||||
);
|
||||
res.status(401).json({
|
||||
ok: false,
|
||||
code: "AUTH_FAILED",
|
||||
message: "gateway token 无效"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function requireSyncUser(req: SyncAuthedRequest, res: Response, next: NextFunction): void {
|
||||
if (!ensureSyncEnabled(res)) return;
|
||||
const auth = String(req.headers.authorization || "");
|
||||
const token = auth.startsWith("Bearer ") ? auth.slice("Bearer ".length).trim() : "";
|
||||
if (!token) {
|
||||
logger.warn({ path: req.path }, "小程序同步请求缺少 Bearer token");
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_MISSING", message: "缺少同步令牌" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = verifySyncToken(token);
|
||||
req.syncUser = {
|
||||
userId: payload.uid,
|
||||
openid: payload.oid
|
||||
};
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{
|
||||
path: req.path,
|
||||
err: error
|
||||
},
|
||||
"小程序同步请求鉴权失败"
|
||||
);
|
||||
res.status(401).json({
|
||||
ok: false,
|
||||
code: "SYNC_TOKEN_INVALID",
|
||||
message: error instanceof Error ? error.message : "同步令牌无效"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSyncRoutes(app: Express): void {
|
||||
const repository = new SyncRepository();
|
||||
registerMiniprogramTtsRoutes(app, requireSyncUser);
|
||||
|
||||
app.post("/api/miniprogram/auth/login", async (req, res) => {
|
||||
if (!ensureSyncEnabled(res) || !requireGatewayToken(req, res)) return;
|
||||
const parsed = syncLoginBodySchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
code: "INVALID_BODY",
|
||||
message: "登录参数不合法"
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await loginMiniprogramUser(parsed.data.code, repository);
|
||||
res.json({
|
||||
ok: true,
|
||||
token: result.token,
|
||||
expiresAt: result.expiresAt,
|
||||
user: {
|
||||
id: result.user.id
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn({ err: error }, "小程序同步登录失败");
|
||||
res.status(502).json({
|
||||
ok: false,
|
||||
code: "WECHAT_LOGIN_FAILED",
|
||||
message: error instanceof Error ? error.message : "微信登录失败"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/miniprogram/sync/bootstrap", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
ok: true,
|
||||
settings: repository.getSettings(userId),
|
||||
servers: repository.listServers(userId),
|
||||
records: repository.listRecords(userId)
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/miniprogram/sync/settings", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
res.json({ ok: true, settings: repository.getSettings(userId) });
|
||||
});
|
||||
|
||||
app.put("/api/miniprogram/sync/settings", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
const parsed = syncSettingsPayloadSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ ok: false, code: "INVALID_BODY", message: "settings 参数不合法" });
|
||||
return;
|
||||
}
|
||||
repository.upsertSettings(userId, parsed.data);
|
||||
res.json({ ok: true, settings: repository.getSettings(userId) });
|
||||
});
|
||||
|
||||
app.get("/api/miniprogram/sync/servers", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
res.json({ ok: true, servers: repository.listServers(userId) });
|
||||
});
|
||||
|
||||
app.put("/api/miniprogram/sync/servers", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
const parsed = syncServersPayloadSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ ok: false, code: "INVALID_BODY", message: "servers 参数不合法" });
|
||||
return;
|
||||
}
|
||||
repository.upsertServers(userId, parsed.data.servers);
|
||||
res.json({ ok: true, servers: repository.listServers(userId) });
|
||||
});
|
||||
|
||||
app.get("/api/miniprogram/sync/records", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
res.json({ ok: true, records: repository.listRecords(userId) });
|
||||
});
|
||||
|
||||
app.put("/api/miniprogram/sync/records", requireSyncUser, (req: SyncAuthedRequest, res) => {
|
||||
const userId = req.syncUser?.userId;
|
||||
if (!userId) {
|
||||
res.status(401).json({ ok: false, code: "SYNC_TOKEN_INVALID", message: "同步令牌无效" });
|
||||
return;
|
||||
}
|
||||
const parsed = syncRecordsPayloadSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ ok: false, code: "INVALID_BODY", message: "records 参数不合法" });
|
||||
return;
|
||||
}
|
||||
repository.upsertRecords(userId, parsed.data.records);
|
||||
res.json({ ok: true, records: repository.listRecords(userId) });
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user