migrate from SQLite to PostgreSQL with Drizzle ORM

- Updated all packages to latest versions (React 19, Next.js 14.2.32)
- Replaced sqlite3 with pg and drizzle-orm dependencies
- Created complete PostgreSQL schema with relationships and indexes
- Migrated all API endpoints from SQLite to Drizzle queries
- Added database seeding with sample data
- Updated authentication to use bcrypt instead of pbkdf2
- Configured connection pooling for PostgreSQL
- Updated app version to 1.0.0
- All endpoints tested and working correctly
This commit is contained in:
2025-09-06 12:56:33 +02:00
parent 52bde64e7f
commit 860070a302
26 changed files with 2526 additions and 2403 deletions

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import sqlite3 from 'sqlite3'
import path from 'path'
import { db, schema } from '../../lib/db/connection'
import { eq, gte, count, desc, sql } from 'drizzle-orm'
interface PublicStats {
total_sources: number
@@ -18,91 +18,69 @@ export default async function handler(
return res.status(405).json({ error: 'Method not allowed' })
}
const dbPath = path.join(process.cwd(), 'database', 'antihoax.db')
const db = new sqlite3.Database(dbPath)
try {
// Get basic counts
const totalSources = await new Promise<number>((resolve, reject) => {
db.get(
"SELECT COUNT(*) as count FROM sources WHERE status = 'verified'",
(err, row: any) => {
if (err) reject(err)
else resolve(row.count)
}
)
})
const [totalSourcesResult] = await db
.select({ count: count() })
.from(schema.sources)
.where(eq(schema.sources.status, 'verified'))
const highRiskSources = await new Promise<number>((resolve, reject) => {
db.get(
"SELECT COUNT(*) as count FROM sources WHERE status = 'verified' AND risk_level >= 4",
(err, row: any) => {
if (err) reject(err)
else resolve(row.count)
}
const [highRiskSourcesResult] = await db
.select({ count: count() })
.from(schema.sources)
.where(
sql`${schema.sources.status} = 'verified' AND ${schema.sources.riskLevel} >= 4`
)
})
const recentAdditions = await new Promise<number>((resolve, reject) => {
db.get(
"SELECT COUNT(*) as count FROM sources WHERE created_at > datetime('now', '-30 days')",
(err, row: any) => {
if (err) reject(err)
else resolve(row.count)
}
)
})
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
const [recentAdditionsResult] = await db
.select({ count: count() })
.from(schema.sources)
.where(gte(schema.sources.createdAt, thirtyDaysAgo))
// Get categories breakdown
const categoriesBreakdown = await new Promise<{ [key: string]: number }>((resolve, reject) => {
db.all(
`SELECT c.name, COUNT(*) as count
FROM categories c
JOIN source_categories sc ON c.id = sc.category_id
JOIN sources s ON sc.source_id = s.id
WHERE s.status = 'verified'
GROUP BY c.id, c.name`,
(err, rows: any[]) => {
if (err) reject(err)
else {
const breakdown: { [key: string]: number } = {}
rows.forEach(row => {
breakdown[row.name] = row.count
})
resolve(breakdown)
}
}
)
const categoriesBreakdownResult = await db
.select({
name: schema.categories.name,
count: count()
})
.from(schema.categories)
.innerJoin(schema.sourceCategories, eq(schema.categories.id, schema.sourceCategories.categoryId))
.innerJoin(schema.sources, eq(schema.sourceCategories.sourceId, schema.sources.id))
.where(eq(schema.sources.status, 'verified'))
.groupBy(schema.categories.id, schema.categories.name)
const categoriesBreakdown: { [key: string]: number } = {}
categoriesBreakdownResult.forEach(row => {
categoriesBreakdown[row.name] = row.count
})
// Get top risky domains
const topDomains = await new Promise<{ domain: string; count: number; risk_level: number }[]>((resolve, reject) => {
db.all(
`SELECT domain, COUNT(*) as count, AVG(risk_level) as avg_risk
FROM sources
WHERE status = 'verified'
GROUP BY domain
ORDER BY avg_risk DESC, count DESC
LIMIT 10`,
(err, rows: any[]) => {
if (err) reject(err)
else {
const domains = rows.map(row => ({
domain: row.domain,
count: row.count,
risk_level: Math.round(row.avg_risk * 10) / 10
}))
resolve(domains)
}
}
)
})
const topDomainsResult = await db
.select({
domain: schema.sources.domain,
count: count(),
avgRisk: sql<number>`AVG(${schema.sources.riskLevel})`
})
.from(schema.sources)
.where(eq(schema.sources.status, 'verified'))
.groupBy(schema.sources.domain)
.orderBy(desc(sql`AVG(${schema.sources.riskLevel})`), desc(count()))
.limit(10)
const topDomains = topDomainsResult.map(row => ({
domain: row.domain,
count: row.count,
risk_level: Math.round(row.avgRisk * 10) / 10
}))
const stats: PublicStats = {
total_sources: totalSources,
high_risk_sources: highRiskSources,
total_sources: totalSourcesResult.count,
high_risk_sources: highRiskSourcesResult.count,
categories_breakdown: categoriesBreakdown,
recent_additions: recentAdditions,
recent_additions: recentAdditionsResult.count,
top_domains: topDomains
}
@@ -111,7 +89,5 @@ export default async function handler(
} catch (error) {
console.error('Database error:', error)
return res.status(500).json({ error: 'Internal server error' })
} finally {
db.close()
}
}