import type { APIRoute } from 'astro'; import crypto from 'crypto'; const PB_POCKETBASE_URL = import.meta.env.PB_POCKETBASE_URL || 'http://127.0.0.1:8090'; const POCKETBASE_ID_REGEX = /^[a-z0-9]{15}$/; function getClientIp(request: Request): string { const forwarded = request.headers.get('x-forwarded-for'); if (forwarded) { return forwarded.split(',')[0].trim(); } const realIp = request.headers.get('x-real-ip'); if (realIp) { return realIp; } // Для localhost использовать заголовок Host или default const host = request.headers.get('host') || 'localhost'; return host.includes('localhost') || host.includes('127.0.0.1') ? 'localhost' : 'unknown'; } function generateVisitorHash(ip: string, userAgent: string): string { return crypto.createHash('sha256').update(ip + userAgent).digest('hex').slice(0, 32); } async function pbRequest(method: string, path: string, body?: object) { const url = `${PB_POCKETBASE_URL}${path}`; const options: RequestInit = { method, headers: { 'Content-Type': 'application/json' }, }; if (body) options.body = JSON.stringify(body); const res = await fetch(url, options); if (!res.ok) { const err = await res.text(); throw new Error(`PB ${method} ${path}: ${res.status} - ${err}`); } return res.json(); } function jsonResponse(data: object, status = 200): Response { return new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } }); } export const GET: APIRoute = async ({ request, url }) => { try { const postId = url.searchParams.get('postId'); if (!postId || !POCKETBASE_ID_REGEX.test(postId)) { return jsonResponse({ error: 'Некорректный postId' }, 400); } let post; try { post = await pbRequest('GET', `/api/collections/posts/records/${postId}`); } catch { return jsonResponse({ error: 'Пост не найден' }, 404); } const ip = getClientIp(request); const userAgent = request.headers.get('user-agent') || 'unknown'; const visitorHash = generateVisitorHash(ip, userAgent); const now = new Date(); const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); const yesterdayStr = yesterday.toISOString(); let existingViews; try { const res = await pbRequest('GET', `/api/collections/post_views/records?filter=(post="${postId}")&&(visitor_hash="${visitorHash}")&&(created>="${yesterdayStr}")&perPage=1`); existingViews = { totalItems: res.totalItems || 0 }; } catch { existingViews = { totalItems: 0 }; } let isNewView = false; if (existingViews.totalItems === 0) { isNewView = true; try { await pbRequest('POST', '/api/collections/post_views/records', { post: postId, visitor_hash: visitorHash, ip: ip, user_agent: userAgent, }); } catch { // ignore } } let totalViews = post.views || 0; if (isNewView) { totalViews += 1; try { await pbRequest('PATCH', `/api/collections/posts/records/${postId}`, { views: totalViews }); } catch { // ignore } } return jsonResponse({ views: totalViews, isNewView }, 200); } catch (error) { console.error('[Increment Views] Error:', error); return jsonResponse({ error: 'Внутренняя ошибка сервера' }, 500); } };