157 lines
3.4 KiB
TypeScript
157 lines
3.4 KiB
TypeScript
import { executeQuery, executeQuerySingle } from '@/lib/clickhouse';
|
|
import { Link, LinkQueryParams } from '../types';
|
|
|
|
/**
|
|
* Find links with filtering options
|
|
*/
|
|
export async function findLinks({
|
|
limit = 10,
|
|
offset = 0,
|
|
searchTerm = '',
|
|
tagFilter = '',
|
|
isActive = null,
|
|
}: LinkQueryParams) {
|
|
// Build WHERE conditions
|
|
const conditions = [];
|
|
|
|
if (searchTerm) {
|
|
conditions.push(`
|
|
(lower(title) LIKE lower('%${searchTerm}%') OR
|
|
lower(original_url) LIKE lower('%${searchTerm}%'))
|
|
`);
|
|
}
|
|
|
|
if (tagFilter) {
|
|
conditions.push(`hasAny(tags, ['${tagFilter}'])`);
|
|
}
|
|
|
|
if (isActive !== null) {
|
|
conditions.push(`is_active = ${isActive ? 'true' : 'false'}`);
|
|
}
|
|
|
|
const whereClause = conditions.length > 0
|
|
? `WHERE ${conditions.join(' AND ')}`
|
|
: '';
|
|
|
|
// Get total count
|
|
const countQuery = `
|
|
SELECT count() as total
|
|
FROM links
|
|
${whereClause}
|
|
`;
|
|
|
|
const countData = await executeQuery<{ total: number }>(countQuery);
|
|
const total = countData.length > 0 ? countData[0].total : 0;
|
|
|
|
// 使用左连接获取链接数据和统计信息
|
|
const linksQuery = `
|
|
SELECT
|
|
l.link_id,
|
|
l.original_url,
|
|
l.created_at,
|
|
l.created_by,
|
|
l.title,
|
|
l.description,
|
|
l.tags,
|
|
l.is_active,
|
|
l.expires_at,
|
|
l.team_id,
|
|
l.project_id,
|
|
count(le.event_id) as visits,
|
|
count(DISTINCT le.visitor_id) as unique_visits
|
|
FROM links l
|
|
LEFT JOIN link_events le ON l.link_id = le.link_id
|
|
${whereClause}
|
|
GROUP BY
|
|
l.link_id,
|
|
l.original_url,
|
|
l.created_at,
|
|
l.created_by,
|
|
l.title,
|
|
l.description,
|
|
l.tags,
|
|
l.is_active,
|
|
l.expires_at,
|
|
l.team_id,
|
|
l.project_id
|
|
ORDER BY l.created_at DESC
|
|
LIMIT ${limit}
|
|
OFFSET ${offset}
|
|
`;
|
|
|
|
const links = await executeQuery<Link>(linksQuery);
|
|
|
|
return {
|
|
links,
|
|
total,
|
|
limit,
|
|
offset,
|
|
page: Math.floor(offset / limit) + 1,
|
|
totalPages: Math.ceil(total / limit)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Find a single link by ID
|
|
*/
|
|
export async function findLinkById(linkId: string): Promise<Link | null> {
|
|
const query = `
|
|
SELECT
|
|
l.link_id,
|
|
l.original_url,
|
|
l.created_at,
|
|
l.created_by,
|
|
l.title,
|
|
l.description,
|
|
l.tags,
|
|
l.is_active,
|
|
l.expires_at,
|
|
l.team_id,
|
|
l.project_id,
|
|
count(le.event_id) as visits,
|
|
count(DISTINCT le.visitor_id) as unique_visits
|
|
FROM links l
|
|
LEFT JOIN link_events le ON l.link_id = le.link_id
|
|
WHERE l.link_id = '${linkId}'
|
|
GROUP BY
|
|
l.link_id,
|
|
l.original_url,
|
|
l.created_at,
|
|
l.created_by,
|
|
l.title,
|
|
l.description,
|
|
l.tags,
|
|
l.is_active,
|
|
l.expires_at,
|
|
l.team_id,
|
|
l.project_id
|
|
LIMIT 1
|
|
`;
|
|
|
|
return await executeQuerySingle<Link>(query);
|
|
}
|
|
|
|
/**
|
|
* Find a single link by ID - only basic info without statistics
|
|
*/
|
|
export async function findLinkDetailsById(linkId: string): Promise<Omit<Link, 'visits' | 'unique_visits'> | null> {
|
|
const query = `
|
|
SELECT
|
|
link_id,
|
|
original_url,
|
|
created_at,
|
|
created_by,
|
|
title,
|
|
description,
|
|
tags,
|
|
is_active,
|
|
expires_at,
|
|
team_id,
|
|
project_id
|
|
FROM links
|
|
WHERE link_id = '${linkId}'
|
|
LIMIT 1
|
|
`;
|
|
|
|
return await executeQuerySingle<Omit<Link, 'visits' | 'unique_visits'>>(query);
|
|
}
|