118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
import uuid
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db
|
|
from app.models import Playlist, Vote, VotingConfig
|
|
|
|
router = APIRouter(prefix="/voting", tags=["voting"])
|
|
|
|
COOLDOWN_SECONDS = 10
|
|
|
|
|
|
def _voter_token(request: Request) -> str:
|
|
token = request.session.get("voter_token")
|
|
if not token:
|
|
token = str(uuid.uuid4())
|
|
request.session["voter_token"] = token
|
|
return token
|
|
|
|
|
|
def _is_open(config: VotingConfig | None) -> bool:
|
|
if not config or not config.is_active:
|
|
return False
|
|
now = datetime.now().strftime("%H:%M")
|
|
return config.start_time <= now <= config.end_time
|
|
|
|
|
|
def _cooldown_remaining(db: Session, voter_token: str) -> int:
|
|
"""Segundos restantes de cooldown. 0 si puede votar."""
|
|
last = (
|
|
db.query(Vote)
|
|
.filter(Vote.voter_token == voter_token)
|
|
.order_by(Vote.voted_at.desc())
|
|
.first()
|
|
)
|
|
if not last:
|
|
return 0
|
|
voted_at = last.voted_at
|
|
if voted_at.tzinfo is None:
|
|
voted_at = voted_at.replace(tzinfo=timezone.utc)
|
|
elapsed = (datetime.now(timezone.utc) - voted_at).total_seconds()
|
|
return max(0, int(COOLDOWN_SECONDS - elapsed))
|
|
|
|
|
|
@router.get("/status")
|
|
def voting_status(request: Request, db: Session = Depends(get_db)):
|
|
config = db.query(VotingConfig).first()
|
|
voter_token = _voter_token(request)
|
|
is_open = _is_open(config)
|
|
|
|
playlists = db.query(Playlist).order_by(Playlist.created_at.desc()).all()
|
|
|
|
vote_counts: dict[int, int] = {
|
|
pl.id: db.query(Vote).filter(Vote.playlist_id == pl.id).count()
|
|
for pl in playlists
|
|
}
|
|
|
|
last_vote = (
|
|
db.query(Vote)
|
|
.filter(Vote.voter_token == voter_token)
|
|
.order_by(Vote.voted_at.desc())
|
|
.first()
|
|
)
|
|
|
|
return {
|
|
"is_open": is_open,
|
|
"cooldown_seconds": COOLDOWN_SECONDS,
|
|
"cooldown_remaining": _cooldown_remaining(db, voter_token),
|
|
"config": {
|
|
"start_time": config.start_time if config else None,
|
|
"end_time": config.end_time if config else None,
|
|
"is_active": config.is_active if config else False,
|
|
},
|
|
"my_last_vote_playlist_id": last_vote.playlist_id if last_vote else None,
|
|
"playlists": [
|
|
{
|
|
"id": pl.id,
|
|
"spotify_id": pl.spotify_id,
|
|
"spotify_type": pl.spotify_type,
|
|
"name": pl.name,
|
|
"image_url": pl.image_url,
|
|
"emoji": pl.emoji or "",
|
|
"description": pl.description or "",
|
|
"votes": vote_counts.get(pl.id, 0),
|
|
}
|
|
for pl in playlists
|
|
],
|
|
}
|
|
|
|
|
|
@router.post("/vote/{playlist_id}")
|
|
def cast_vote(playlist_id: int, request: Request, db: Session = Depends(get_db)):
|
|
config = db.query(VotingConfig).first()
|
|
if not _is_open(config):
|
|
raise HTTPException(status_code=403, detail="La votación no está abierta en este momento")
|
|
|
|
pl = db.query(Playlist).filter(Playlist.id == playlist_id).first()
|
|
if not pl:
|
|
raise HTTPException(status_code=404, detail="Playlist no encontrada")
|
|
|
|
voter_token = _voter_token(request)
|
|
remaining = _cooldown_remaining(db, voter_token)
|
|
if remaining > 0:
|
|
raise HTTPException(
|
|
status_code=429,
|
|
detail={"message": "Debes esperar antes de votar de nuevo", "remaining": remaining},
|
|
)
|
|
|
|
db.add(Vote(
|
|
playlist_id=playlist_id,
|
|
voter_token=voter_token,
|
|
voted_at=datetime.now(timezone.utc),
|
|
))
|
|
db.commit()
|
|
return {"ok": True, "cooldown_seconds": COOLDOWN_SECONDS}
|