Files
infohliadka/pages/api/sources/check.ts
Lukas Davidovic 249a672cd7 transform admin panel with comprehensive professional UI
- migrate from SQLite to PostgreSQL with Drizzle ORM
- implement comprehensive AdminLayout with expandable sidebar navigation
- create professional dashboard with real-time charts and metrics
- add advanced monitoring, reporting, and export functionality
- fix menu alignment and remove non-existent pages
- eliminate duplicate headers and improve UI consistency
- add Tailwind CSS v3 for professional styling
- expand database schema from 6 to 15 tables
- implement role-based access control and API key management
- create comprehensive settings, monitoring, and system info pages
2025-09-06 15:14:20 +02:00

158 lines
4.6 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from 'next'
import { db, schema } from '../../../lib/db/connection'
import { eq, and, sql } from 'drizzle-orm'
import { rateLimit, getRateLimitHeaders } from '../../../lib/rate-limiter'
import { cache, getCacheKey } from '../../../lib/cache'
type CheckResponse = {
is_problematic: boolean
risk_level: number
categories: string[]
message: string
source_count: number
}
function extractDomain(url: string): string {
try {
// Clean up the URL first
let cleanUrl = url.trim()
if (!cleanUrl.startsWith('http://') && !cleanUrl.startsWith('https://')) {
cleanUrl = 'https://' + cleanUrl
}
const urlObj = new URL(cleanUrl)
let domain = urlObj.hostname.toLowerCase()
// Remove common prefixes
domain = domain.replace(/^www\./, '')
domain = domain.replace(/^m\./, '')
domain = domain.replace(/^mobile\./, '')
domain = domain.replace(/^amp\./, '')
// Handle subdomains for known patterns - extract main domain for common TLDs
if (domain.includes('.')) {
const parts = domain.split('.')
if (parts.length > 2) {
const tld = parts[parts.length - 1]
const sld = parts[parts.length - 2]
// Extract main domain for common TLDs
if (['com', 'org', 'net', 'sk', 'cz', 'hu', 'pl', 'eu', 'info'].includes(tld)) {
domain = `${sld}.${tld}`
}
}
}
return domain
} catch {
return ''
}
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<CheckResponse | { error: string }>
) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' })
}
// Rate limiting
const clientIp = req.headers['x-forwarded-for'] || req.connection?.remoteAddress || 'unknown'
const rateLimitResult = rateLimit(clientIp.toString())
const headers = getRateLimitHeaders(rateLimitResult)
Object.entries(headers).forEach(([key, value]) => {
res.setHeader(key, value)
})
if (!rateLimitResult.allowed) {
return res.status(429).json({ error: 'Too many requests' })
}
const { url } = req.query
if (!url || typeof url !== 'string') {
return res.status(400).json({ error: 'URL parameter is required' })
}
const domain = extractDomain(url)
if (!domain) {
return res.status(400).json({ error: 'Invalid URL format' })
}
// Check cache first
const cacheKey = getCacheKey('domain_check', domain)
const cachedResult = cache.get<CheckResponse>(cacheKey)
if (cachedResult) {
return res.status(200).json(cachedResult)
}
try {
const sources = await db
.select({
id: schema.sources.id,
riskLevel: schema.sources.riskLevel,
categories: sql<string>`string_agg(${schema.categories.name}, ',')`
})
.from(schema.sources)
.leftJoin(schema.sourceCategories, eq(schema.sources.id, schema.sourceCategories.sourceId))
.leftJoin(schema.categories, eq(schema.sourceCategories.categoryId, schema.categories.id))
.where(
and(
eq(schema.sources.domain, domain),
eq(schema.sources.status, 'verified')
)
)
.groupBy(schema.sources.id, schema.sources.riskLevel)
let result: CheckResponse
if (sources.length === 0) {
result = {
is_problematic: false,
risk_level: 0,
categories: [],
message: 'Stránka nie je v našej databáze problematických zdrojov',
source_count: 0
}
} else {
const maxRiskLevel = Math.max(...sources.map(s => s.riskLevel || 0))
const allCategories = sources
.map(s => s.categories)
.filter(Boolean)
.join(',')
.split(',')
.filter(Boolean)
const uniqueCategories = Array.from(new Set(allCategories))
let message = ''
if (maxRiskLevel >= 4) {
message = 'VYSOKÉ RIZIKO: Táto stránka šíri nebezpečné obsahy'
} else if (maxRiskLevel >= 3) {
message = 'STREDNÉ RIZIKO: Táto stránka môže obsahovať problematické informácie'
} else {
message = 'NÍZKE RIZIKO: Táto stránka je označená ako problematická'
}
result = {
is_problematic: true,
risk_level: maxRiskLevel,
categories: uniqueCategories,
message,
source_count: sources.length
}
}
// Cache the result (5 minutes for non-problematic, 15 minutes for problematic)
const cacheTtl = result.is_problematic ? 900 : 300
cache.set(cacheKey, result, cacheTtl)
return res.status(200).json(result)
} catch (error) {
console.error('Database error:', error)
return res.status(500).json({ error: 'Internal server error' })
}
}