- إضافة روابط جديدة في تذييل صفحات الأسعار والمدونة. - إنشاء مكون صفحة الأسعار لعرض تفاصيل الخطط ومقارنة الميزات. - تطوير مكون صفحة المدونة لعرض منشورات المدونة مع روابط للمقالات الفردية. - تقديم مكون تقييم الأدوات لتلقي ملاحظات المستخدمين حول الأدوات، بما في ذلك التقييم بالنجوم والتعليقات الاختيارية. - تفعيل وظيفة useToolRating لجلب وعرض تقييمات الأدوات. - تحديث أدوات تحسين محركات البحث لتضمين بيانات التقييم في البيانات المنظمة للأدوات. - تحسين ملفات i18n بترجمات للميزات والصفحات الجديدة. - دمج إدارة الموافقة على ملفات تعريف الارتباط لتتبع التحليلات.
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
"""Rating service — stores and aggregates user ratings per tool."""
|
|
import logging
|
|
import os
|
|
import sqlite3
|
|
from datetime import datetime, timezone
|
|
|
|
from flask import current_app
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _connect() -> sqlite3.Connection:
|
|
"""Create a SQLite connection."""
|
|
db_path = current_app.config["DATABASE_PATH"]
|
|
db_dir = os.path.dirname(db_path)
|
|
if db_dir:
|
|
os.makedirs(db_dir, exist_ok=True)
|
|
connection = sqlite3.connect(db_path)
|
|
connection.row_factory = sqlite3.Row
|
|
return connection
|
|
|
|
|
|
def _utc_now() -> str:
|
|
return datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
def init_ratings_db():
|
|
"""Create ratings table if it does not exist."""
|
|
with _connect() as conn:
|
|
conn.executescript(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS tool_ratings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
tool TEXT NOT NULL,
|
|
rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5),
|
|
feedback TEXT DEFAULT '',
|
|
tag TEXT DEFAULT '',
|
|
fingerprint TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tool_ratings_tool
|
|
ON tool_ratings(tool);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tool_ratings_fingerprint_tool
|
|
ON tool_ratings(fingerprint, tool);
|
|
"""
|
|
)
|
|
|
|
|
|
def submit_rating(
|
|
tool: str,
|
|
rating: int,
|
|
feedback: str = "",
|
|
tag: str = "",
|
|
fingerprint: str = "",
|
|
) -> None:
|
|
"""Store a rating. Limits one rating per fingerprint per tool per day."""
|
|
now = _utc_now()
|
|
today = now[:10] # YYYY-MM-DD
|
|
|
|
with _connect() as conn:
|
|
# Check for duplicate rating from same fingerprint today
|
|
existing = conn.execute(
|
|
"""SELECT id FROM tool_ratings
|
|
WHERE fingerprint = ? AND tool = ? AND created_at LIKE ?
|
|
LIMIT 1""",
|
|
(fingerprint, tool, f"{today}%"),
|
|
).fetchone()
|
|
|
|
if existing:
|
|
# Update existing rating instead of creating duplicate
|
|
conn.execute(
|
|
"""UPDATE tool_ratings
|
|
SET rating = ?, feedback = ?, tag = ?, created_at = ?
|
|
WHERE id = ?""",
|
|
(rating, feedback, tag, now, existing["id"]),
|
|
)
|
|
else:
|
|
conn.execute(
|
|
"""INSERT INTO tool_ratings (tool, rating, feedback, tag, fingerprint, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)""",
|
|
(tool, rating, feedback, tag, fingerprint, now),
|
|
)
|
|
|
|
|
|
def get_tool_rating_summary(tool: str) -> dict:
|
|
"""Return aggregate rating data for one tool."""
|
|
with _connect() as conn:
|
|
row = conn.execute(
|
|
"""SELECT
|
|
COUNT(*) as count,
|
|
COALESCE(AVG(rating), 0) as average,
|
|
COALESCE(SUM(CASE WHEN rating = 5 THEN 1 ELSE 0 END), 0) as star5,
|
|
COALESCE(SUM(CASE WHEN rating = 4 THEN 1 ELSE 0 END), 0) as star4,
|
|
COALESCE(SUM(CASE WHEN rating = 3 THEN 1 ELSE 0 END), 0) as star3,
|
|
COALESCE(SUM(CASE WHEN rating = 2 THEN 1 ELSE 0 END), 0) as star2,
|
|
COALESCE(SUM(CASE WHEN rating = 1 THEN 1 ELSE 0 END), 0) as star1
|
|
FROM tool_ratings WHERE tool = ?""",
|
|
(tool,),
|
|
).fetchone()
|
|
|
|
return {
|
|
"tool": tool,
|
|
"count": row["count"],
|
|
"average": round(row["average"], 1),
|
|
"distribution": {
|
|
"5": row["star5"],
|
|
"4": row["star4"],
|
|
"3": row["star3"],
|
|
"2": row["star2"],
|
|
"1": row["star1"],
|
|
},
|
|
}
|
|
|
|
|
|
def get_all_ratings_summary() -> list[dict]:
|
|
"""Return aggregated ratings for all tools that have at least one rating."""
|
|
with _connect() as conn:
|
|
rows = conn.execute(
|
|
"""SELECT
|
|
tool,
|
|
COUNT(*) as count,
|
|
COALESCE(AVG(rating), 0) as average
|
|
FROM tool_ratings
|
|
GROUP BY tool
|
|
ORDER BY count DESC"""
|
|
).fetchall()
|
|
|
|
return [
|
|
{
|
|
"tool": row["tool"],
|
|
"count": row["count"],
|
|
"average": round(row["average"], 1),
|
|
}
|
|
for row in rows
|
|
]
|