feat: database backups & analytics site management

- Added manual database backup functionality with pg_dump
- Added cron-based automated backups to Google Drive and Dropbox
- Added ability to manually add, edit, and delete sites in Analytics
- Added 1-day timeframe filter in Analytics page
- Updated Dockerfile to include postgresql-client
This commit is contained in:
mstfyldz
2026-06-02 17:44:46 +03:00
parent e28fa966e3
commit e5dc347a0b
12 changed files with 686 additions and 10 deletions
+49 -1
View File
@@ -33,6 +33,12 @@ pool.query(`
);
CREATE INDEX IF NOT EXISTS idx_pv_domain ON pageviews(domain, ts DESC);
CREATE INDEX IF NOT EXISTS idx_pv_ts ON pageviews(ts DESC);
CREATE TABLE IF NOT EXISTS analytics_sites (
domain TEXT PRIMARY KEY,
created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::BIGINT)
);
CREATE TABLE IF NOT EXISTS config_sites (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
@@ -59,6 +65,25 @@ pool.query(`
icon TEXT,
description TEXT
);
CREATE TABLE IF NOT EXISTS backup_configs (
id TEXT PRIMARY KEY,
db_id TEXT NOT NULL,
schedule TEXT NOT NULL,
cloud_type TEXT NOT NULL,
credentials TEXT NOT NULL,
created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::BIGINT)
);
CREATE TABLE IF NOT EXISTS backup_logs (
id SERIAL PRIMARY KEY,
db_id TEXT NOT NULL,
status TEXT NOT NULL,
message TEXT,
file_size BIGINT,
ts BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::BIGINT)
);
CREATE INDEX IF NOT EXISTS idx_backup_db_ts ON backup_logs(db_id, ts DESC);
`).catch(console.error)
export type PingLog = {
id: number
@@ -192,8 +217,31 @@ export async function getAnalyticsStats(domain: string, days = 30) {
}
export async function getAllDomains(): Promise<string[]> {
const res = await pool.query(`SELECT DISTINCT domain FROM pageviews ORDER BY domain`)
const res = await pool.query(`
SELECT domain FROM analytics_sites
UNION
SELECT DISTINCT domain FROM pageviews
ORDER BY domain
`)
return res.rows.map(r => r.domain)
}
export async function addAnalyticsSite(domain: string) {
await pool.query(
`INSERT INTO analytics_sites (domain) VALUES ($1) ON CONFLICT DO NOTHING`,
[domain]
)
}
export async function renameAnalyticsSite(oldDomain: string, newDomain: string) {
// Update both tables
await pool.query(`UPDATE analytics_sites SET domain = $1 WHERE domain = $2`, [newDomain, oldDomain])
await pool.query(`UPDATE pageviews SET domain = $1 WHERE domain = $2`, [newDomain, oldDomain])
}
export async function deleteAnalyticsSite(domain: string) {
await pool.query(`DELETE FROM analytics_sites WHERE domain = $1`, [domain])
await pool.query(`DELETE FROM pageviews WHERE domain = $1`, [domain])
}
export default pool