Autonomous Publishing System — full stack: orchestrator, 8 vertical sites, admin dashboard, analytics, cron pipeline
This commit is contained in:
101
analytics/collector.py
Normal file
101
analytics/collector.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""
|
||||
Auto Publisher — Analytics collector endpoint.
|
||||
Lightweight Flask app that runs on each site CT to collect pageview data.
|
||||
"""
|
||||
from flask import Flask, request, jsonify
|
||||
import sqlite3
|
||||
import json
|
||||
import hashlib
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DB_PATH = "/var/lib/auto-publisher/analytics.db"
|
||||
|
||||
|
||||
def get_db():
|
||||
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS pageviews (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL,
|
||||
vertical TEXT NOT NULL,
|
||||
referrer TEXT DEFAULT '',
|
||||
user_agent TEXT DEFAULT '',
|
||||
ip_hash TEXT DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
db.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_pageviews_path ON pageviews(path);
|
||||
""")
|
||||
db.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_pageviews_created ON pageviews(created_at);
|
||||
""")
|
||||
db.commit()
|
||||
return db
|
||||
|
||||
|
||||
@app.route("/a/collect", methods=["POST", "GET"])
|
||||
def collect():
|
||||
"""Collect a pageview ping."""
|
||||
path = request.args.get("p", "/")
|
||||
vertical = request.args.get("v", "unknown")
|
||||
ref = request.args.get("r", "")
|
||||
ua = request.headers.get("User-Agent", "")[:200]
|
||||
ip = request.remote_addr or ""
|
||||
ip_hash = hashlib.sha256(ip.encode()).hexdigest()[:16] if ip else ""
|
||||
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"INSERT INTO pageviews (path, vertical, referrer, user_agent, ip_hash) VALUES (?, ?, ?, ?, ?)",
|
||||
(path, vertical, ref, ua, ip_hash)
|
||||
)
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
# Return a 1x1 transparent GIF
|
||||
return b"\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b", 200, {
|
||||
"Content-Type": "image/gif",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
}
|
||||
|
||||
|
||||
@app.route("/a/stats")
|
||||
def stats():
|
||||
"""Get site analytics summary."""
|
||||
db = get_db()
|
||||
db.row_factory = sqlite3.Row
|
||||
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
total = db.execute("SELECT COUNT(*) as c FROM pageviews").fetchone()["c"]
|
||||
today_views = db.execute(
|
||||
"SELECT COUNT(*) as c FROM pageviews WHERE created_at >= ?", (today,)
|
||||
).fetchone()["c"]
|
||||
|
||||
top_pages = db.execute("""
|
||||
SELECT path, COUNT(*) as c FROM pageviews
|
||||
WHERE created_at >= date('now', '-30 days')
|
||||
GROUP BY path ORDER BY c DESC LIMIT 10
|
||||
""").fetchall()
|
||||
|
||||
db.close()
|
||||
|
||||
return jsonify({
|
||||
"total_views": total,
|
||||
"today_views": today_views,
|
||||
"top_pages": [{"path": r["path"], "views": r["c"]} for r in top_pages],
|
||||
})
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5199, debug=False)
|
||||
Reference in New Issue
Block a user