links search

This commit is contained in:
2025-03-31 16:46:33 +08:00
parent d75110d6b8
commit e7b3b735e0
11 changed files with 689 additions and 303 deletions

View File

@@ -1,47 +1,72 @@
import { NextRequest, NextResponse } from 'next/server';
import type { ApiResponse, EventsQueryParams, EventType } from '@/lib/types';
import {
getEvents,
getEventsSummary,
getTimeSeriesData,
getGeoAnalytics,
getDeviceAnalytics
} from '@/lib/analytics';
import { getEvents } from '@/lib/analytics';
// 获取事件列表
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const params: EventsQueryParams = {
startTime: searchParams.get('startTime') || undefined,
endTime: searchParams.get('endTime') || undefined,
eventType: searchParams.get('eventType') as EventType || undefined,
linkId: searchParams.get('linkId') || undefined,
linkSlug: searchParams.get('linkSlug') || undefined,
userId: searchParams.get('userId') || undefined,
teamId: searchParams.get('teamId') || undefined,
projectId: searchParams.get('projectId') || undefined,
// 构建查询参数,所有参数都是可选的
const params: Partial<EventsQueryParams> = {
// 时间范围参数现在是可选的
...(searchParams.has('startTime') && { startTime: searchParams.get('startTime')! }),
...(searchParams.has('endTime') && { endTime: searchParams.get('endTime')! }),
// 其他过滤参数
...(searchParams.has('eventType') && { eventType: searchParams.get('eventType') as EventType }),
...(searchParams.has('linkId') && { linkId: searchParams.get('linkId')! }),
...(searchParams.has('linkSlug') && { linkSlug: searchParams.get('linkSlug')! }),
...(searchParams.has('userId') && { userId: searchParams.get('userId')! }),
...(searchParams.has('teamId') && { teamId: searchParams.get('teamId')! }),
...(searchParams.has('projectId') && { projectId: searchParams.get('projectId')! }),
...(searchParams.has('tags') && {
tags: searchParams.get('tags')!.split(',').filter(Boolean)
}),
...(searchParams.has('searchSlug') && { searchSlug: searchParams.get('searchSlug')! }),
// 分页和排序参数,设置默认值
page: searchParams.has('page') ? parseInt(searchParams.get('page')!, 10) : 1,
pageSize: searchParams.has('pageSize') ? parseInt(searchParams.get('pageSize')!, 10) : 20,
sortBy: searchParams.get('sortBy') || undefined,
sortOrder: (searchParams.get('sortOrder') as 'asc' | 'desc') || undefined
...(searchParams.has('sortBy') && { sortBy: searchParams.get('sortBy')! }),
...(searchParams.has('sortOrder') && {
sortOrder: searchParams.get('sortOrder') as 'asc' | 'desc'
})
};
// 验证 tags 参数
if (params.tags?.some(tag => !tag.trim())) {
return NextResponse.json({
success: false,
error: 'Invalid tags format'
}, { status: 400 });
}
// 获取事件数据
const { events, total } = await getEvents(params);
// 构建响应
const response: ApiResponse<typeof events> = {
success: true,
data: events,
meta: {
total,
page: params.page,
pageSize: params.pageSize
page: params.page || 1,
pageSize: params.pageSize || 20,
filters: {
startTime: params.startTime,
endTime: params.endTime,
tags: params.tags,
teamId: params.teamId,
projectId: params.projectId,
searchSlug: params.searchSlug
}
}
};
return NextResponse.json(response);
} catch (error) {
console.error('Error in GET /events:', error);
const response: ApiResponse<null> = {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'

View File

@@ -0,0 +1,99 @@
/**
* @swagger
* /api/events/tags:
* get:
* summary: 获取标签列表
* description: 获取所有事件中的唯一标签列表。如果提供了 teamId则只返回该团队的标签。
* tags:
* - Events
* parameters:
* - in: query
* name: teamId
* schema:
* type: string
* description: 团队ID可选。如果提供则只返回该团队的标签。
* responses:
* 200:
* description: 成功获取标签列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* data:
* type: array
* items:
* type: object
* properties:
* tag_name:
* type: string
* example: "marketing"
* 500:
* description: 服务器错误
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* error:
* type: string
* example: "Failed to fetch tags"
*/
import { NextRequest, NextResponse } from 'next/server';
import clickhouse from '@/lib/clickhouse';
export async function GET(request: NextRequest) {
try {
// 从 URL 获取查询参数
const searchParams = request.nextUrl.searchParams;
const teamId = searchParams.get('teamId');
// 构建基础查询
let query = `
WITH
JSONExtractArrayRaw(link_tags) as tags_array,
arrayJoin(tags_array) as tag
SELECT DISTINCT
JSONExtractString(tag) as tag_name
FROM events
WHERE link_tags != '[]'
`;
const queryParams: Record<string, string> = {};
// 如果提供了 teamId添加团队过滤条件
if (teamId) {
query += ` AND team_id = {id:String}`;
queryParams.id = teamId;
}
// 添加排序
query += ` ORDER BY tag_name ASC`;
const result = await clickhouse.query({
query,
query_params: queryParams
});
const data = await result.json();
return NextResponse.json({
success: true,
data: data
});
} catch (error) {
console.error('Error fetching tags:', error);
return NextResponse.json(
{ success: false, error: 'Failed to fetch tags' },
{ status: 500 }
);
}
}