[阶段2.1-2.2] 实现 Garmin 数据同步和健康数据查询路由

后端实现:
- 实现 garmin 路由
  - POST /api/garmin/sync 触发 Garmin 数据同步
  - GET /api/garmin/status 获取同步状态
- 实现 health 路由(所有端点都需要认证)
  - GET /api/health/summary 获取健康数据摘要
  - GET /api/health/steps 获取步数数据
  - GET /api/health/heart-rate 获取心率数据
  - GET /api/health/sleep 获取睡眠数据
  - GET /api/health/activities 获取运动数据

依赖:
- 安装 garmin-connect 用于 Garmin API 集成
- 所有健康数据查询都通过认证中间件保护

验收标准已满足:
- 所有路由返回正确的格式
- 认证检查已应用
- 日期范围过滤支持

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-23 12:26:27 +08:00
parent 3b2d0697f0
commit 637347082a
5 changed files with 273 additions and 17 deletions

View File

@@ -16,6 +16,7 @@
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"garmin-connect": "^1.6.2",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.6.0",
"sqlite3": "^5.1.6",

View File

@@ -1,14 +1,76 @@
import express from 'express';
import { syncData, getSyncStatus } from '../services/GarminService';
import { getUserById } from '../services/AuthService';
import { authMiddleware, AuthRequest } from '../middleware/authMiddleware';
const router = express.Router();
// TODO: Implement Garmin API integration
router.post('/sync', (req, res) => {
res.json({ message: 'Garmin sync endpoint' });
/**
* POST /api/garmin/sync
* Trigger Garmin data synchronization (requires authentication)
*/
router.post('/sync', authMiddleware, async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const user = await getUserById(userId);
if (!user) {
return res.status(404).json({
error: {
status: 404,
message: 'User not found',
},
});
}
// Sync Garmin data using stored credentials
const result = await syncData(userId, {
garminEmail: user.garminEmail,
garminPassword: req.body.garminPassword || '',
});
res.json({
success: result.status === 'success',
data: result,
});
} catch (error) {
next(error);
}
});
router.get('/status', (req, res) => {
res.json({ message: 'Garmin sync status endpoint' });
/**
* GET /api/garmin/status
* Get Garmin synchronization status (requires authentication)
*/
router.get('/status', authMiddleware, async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const status = await getSyncStatus(userId);
res.json({
success: true,
data: status,
});
} catch (error) {
next(error);
}
});
export default router;

View File

@@ -1,26 +1,170 @@
import express from 'express';
import { getSummary, getSteps, getHeartRate, getSleep, getActivities } from '../services/HealthService';
import { authMiddleware, AuthRequest } from '../middleware/authMiddleware';
const router = express.Router();
// TODO: Implement health data endpoints
router.get('/summary', (req, res) => {
res.json({ message: 'Health summary endpoint' });
// Apply authentication middleware to all health routes
router.use(authMiddleware);
/**
* GET /api/health/summary
* Get health data summary for a date range
* Query params: startDate, endDate (ISO 8601 format, e.g., 2024-08-23)
*/
router.get('/summary', async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const { startDate, endDate } = req.query;
const data = await getSummary(userId, {
startDate: startDate as string | undefined,
endDate: endDate as string | undefined,
});
res.json({
success: true,
data,
});
} catch (error) {
next(error);
}
});
router.get('/steps', (req, res) => {
res.json({ message: 'Steps data endpoint' });
/**
* GET /api/health/steps
* Get steps data for a date range
* Query params: startDate, endDate
*/
router.get('/steps', async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const { startDate, endDate } = req.query;
const data = await getSteps(userId, {
startDate: startDate as string | undefined,
endDate: endDate as string | undefined,
});
res.json({
success: true,
data,
});
} catch (error) {
next(error);
}
});
router.get('/heart-rate', (req, res) => {
res.json({ message: 'Heart rate data endpoint' });
/**
* GET /api/health/heart-rate
* Get heart rate data for a date range
* Query params: startDate, endDate
*/
router.get('/heart-rate', async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const { startDate, endDate } = req.query;
const data = await getHeartRate(userId, {
startDate: startDate as string | undefined,
endDate: endDate as string | undefined,
});
res.json({
success: true,
data,
});
} catch (error) {
next(error);
}
});
router.get('/sleep', (req, res) => {
res.json({ message: 'Sleep data endpoint' });
/**
* GET /api/health/sleep
* Get sleep data for a date range
* Query params: startDate, endDate
*/
router.get('/sleep', async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const { startDate, endDate } = req.query;
const data = await getSleep(userId, {
startDate: startDate as string | undefined,
endDate: endDate as string | undefined,
});
res.json({
success: true,
data,
});
} catch (error) {
next(error);
}
});
router.get('/activities', (req, res) => {
res.json({ message: 'Activities endpoint' });
/**
* GET /api/health/activities
* Get activities for a date range
* Query params: startDate, endDate
*/
router.get('/activities', async (req: AuthRequest, res, next) => {
try {
const userId = req.userId;
if (!userId) {
return res.status(401).json({
error: {
status: 401,
message: 'User not authenticated',
},
});
}
const { startDate, endDate } = req.query;
const data = await getActivities(userId, {
startDate: startDate as string | undefined,
endDate: endDate as string | undefined,
});
res.json({
success: true,
data,
});
} catch (error) {
next(error);
}
});
export default router;