feat: registre des votes feature_votes #94
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+11
@@ -76,6 +76,13 @@ cache.init_app(
|
|||||||
|
|
||||||
app: Dash = Dash(
|
app: Dash = Dash(
|
||||||
server=server,
|
server=server,
|
||||||
|
# name="src" (et non "src.app") pour que use_pages enregistre les pages sous
|
||||||
|
# le namespace `src.pages.*`, identique aux imports inter-pages (ex.
|
||||||
|
# inscription.py: `from src.pages.connexion import linkedin_button`). Sinon
|
||||||
|
# Dash découvre `pages.connexion` tandis que l'import explicite crée
|
||||||
|
# `src.pages.connexion` : deux identités, même URL → "duplicate paths" →
|
||||||
|
# check_for_duplicate_pathnames lève à chaque requête → 500 sur toute l'app.
|
||||||
|
name="src",
|
||||||
title="decp.info",
|
title="decp.info",
|
||||||
use_pages=True,
|
use_pages=True,
|
||||||
suppress_callback_exceptions=True,
|
suppress_callback_exceptions=True,
|
||||||
@@ -109,6 +116,10 @@ from src.saved_views import db as saved_views_db # noqa: E402
|
|||||||
|
|
||||||
saved_views_db.init_schema()
|
saved_views_db.init_schema()
|
||||||
|
|
||||||
|
from src.roadmap import db as roadmap_db # noqa: E402
|
||||||
|
|
||||||
|
roadmap_db.init_schema()
|
||||||
|
|
||||||
|
|
||||||
# robots.txt
|
# robots.txt
|
||||||
@app.server.route("/robots.txt")
|
@app.server.route("/robots.txt")
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from src.auth.db import get_conn
|
||||||
|
|
||||||
|
SCHEMA = """
|
||||||
|
CREATE TABLE IF NOT EXISTS feature_votes (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
issue_number INTEGER NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_feature_votes_issue
|
||||||
|
ON feature_votes(issue_number);
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _now() -> str:
|
||||||
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def init_schema() -> None:
|
||||||
|
get_conn().executescript(SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def record_vote(user_id: int, issue_number: int) -> None:
|
||||||
|
get_conn().execute(
|
||||||
|
"INSERT INTO feature_votes (user_id, issue_number, created_at) "
|
||||||
|
"VALUES (?, ?, ?)",
|
||||||
|
(user_id, issue_number, _now()),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def vote_counts() -> dict[int, int]:
|
||||||
|
rows = (
|
||||||
|
get_conn()
|
||||||
|
.execute(
|
||||||
|
"SELECT issue_number, COUNT(*) FROM feature_votes GROUP BY issue_number"
|
||||||
|
)
|
||||||
|
.fetchall()
|
||||||
|
)
|
||||||
|
return {row[0]: row[1] for row in rows}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
# Dash minimal pour que register_page() fonctionne dans les tests de pages de ce
|
||||||
|
# répertoire (CONFIG peuplé). Instancié une seule fois, ici, avant tout import de page.
|
||||||
|
from dash import Dash as _Dash
|
||||||
|
|
||||||
|
_Dash(__name__, use_pages=True, pages_folder="", assets_folder="assets")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def users_db_path(monkeypatch, tmp_path):
|
||||||
|
from src.auth.db import reset_conn_for_tests
|
||||||
|
|
||||||
|
db_path = tmp_path / "users.test.sqlite"
|
||||||
|
monkeypatch.setenv("USERS_DB_PATH", str(db_path))
|
||||||
|
reset_conn_for_tests()
|
||||||
|
yield db_path
|
||||||
|
reset_conn_for_tests()
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from src.auth import db as auth_db
|
||||||
|
from src.roadmap import db as roadmap_db
|
||||||
|
|
||||||
|
|
||||||
|
def _make_user(email="u@ex.fr"):
|
||||||
|
auth_db.init_schema()
|
||||||
|
return auth_db.create_user(email, "hash")
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_schema_creates_feature_votes(users_db_path):
|
||||||
|
roadmap_db.init_schema()
|
||||||
|
conn = auth_db.get_conn()
|
||||||
|
tables = {
|
||||||
|
row[0]
|
||||||
|
for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||||
|
}
|
||||||
|
assert "feature_votes" in tables
|
||||||
|
|
||||||
|
|
||||||
|
def test_record_vote_and_counts(users_db_path):
|
||||||
|
roadmap_db.init_schema()
|
||||||
|
uid = _make_user()
|
||||||
|
roadmap_db.record_vote(uid, 42)
|
||||||
|
roadmap_db.record_vote(uid, 42) # vote multiple autorisé
|
||||||
|
roadmap_db.record_vote(uid, 7)
|
||||||
|
assert roadmap_db.vote_counts() == {42: 2, 7: 1}
|
||||||
|
|
||||||
|
|
||||||
|
def test_vote_counts_empty(users_db_path):
|
||||||
|
roadmap_db.init_schema()
|
||||||
|
assert roadmap_db.vote_counts() == {}
|
||||||
Reference in New Issue
Block a user