This commit is contained in:
2024-06-09 20:41:57 +03:00
parent 175d2f9cd4
commit 3fb38cac3a
17 changed files with 549 additions and 39 deletions

View File

@ -1,4 +1,4 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, types
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
import uuid
@ -7,6 +7,21 @@ import datetime
from .database import Base
class ChoiceType(types.TypeDecorator):
impl = types.String
def __init__(self, choices, **kw):
self.choices = dict(choices)
super(ChoiceType, self).__init__(**kw)
def process_bind_param(self, value, dialect):
return [k for k, v in self.choices.items() if v == value][0]
def process_result_value(self, value, dialect):
return self.choices[value]
class User(Base):
__tablename__ = "users"
@ -47,8 +62,20 @@ class Queue(Base):
description = Column(String, index=True)
owner_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
start_time = Column(DateTime, nullable=True)
status = Column(
ChoiceType(
{
"created": "created",
"waiting": "waiting",
"active": "active",
"finished": "finished",
}
),
nullable=False,
)
users = relationship("QueueUser", backref="queue", lazy="dynamic")
logs = relationship("QueueLog", backref="queue", lazy="dynamic")
class QueueUser(Base):
@ -66,6 +93,7 @@ class QueueLog(Base):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
action = Column(String)
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
created = Column(DateTime, default=datetime.datetime.utcnow)

View File

@ -72,8 +72,10 @@ async def register(
@router.get("/me")
async def read_users_me(
current_user: Annotated[schemas.User, Depends(services.get_current_active_user)],
) -> schemas.User:
current_user: Annotated[
schemas.UserInDB, Depends(services.get_current_active_user)
],
) -> schemas.UserInDB:
return current_user

View File

@ -1,6 +1,7 @@
from datetime import datetime, timedelta, timezone
from typing import Annotated, Union
from sqlalchemy.orm import Session
import redis
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
@ -9,7 +10,8 @@ from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
from ...config import jwt_config
from ...dependencies import get_db
from ...dependencies import get_db, get_redis
from ...db import models
from . import schemas
from . import services
@ -25,8 +27,15 @@ router = APIRouter(
)
@router.get("/owned")
async def owned_queues_list(
queues: Annotated[schemas.Queue, Depends(services.get_owned_queues)],
) -> list[schemas.QueueInDb]:
return queues
@router.get("/")
async def user_queues_list(
async def anonuser_queues_list(
queues: Annotated[schemas.Queue, Depends(services.get_user_queues)],
) -> list[schemas.QueueInDb]:
return queues
@ -60,3 +69,10 @@ async def listen_queue(
updated_queue: Annotated[schemas.QueueDetail, Depends(services.set_queue_listener)]
) -> schemas.QueueDetail:
return updated_queue
@router.post("/{queue_id}/action/{action}")
async def perform_queue_action(
result: Annotated[schemas.ActionResult, Depends(services.action_wrapper)]
):
return result

View File

@ -41,4 +41,14 @@ class QueueInDb(Queue):
class QueueDetail(Queue):
id: UUID
status: str
owner_id: UUID
participants: ParticipantInfo
class ActionResult(BaseModel):
action: str
result: str
class Config:
from_attributes = True

View File

@ -14,24 +14,38 @@ from ..auth import schemas as auth_schemas
from . import schemas
def get_queue_by_id(queue_id: UUID, db: Session) -> models.Queue:
def get_queue_by_id(
queue_id: UUID, db: Annotated[Session, Depends(get_db)]
) -> models.Queue:
q = db.query(models.Queue).filter(models.Queue.id == queue_id).first()
return q
def get_user_queues(
def get_owned_queues(
current_user: Annotated[auth_schemas.User, Depends(auth_services.get_current_user)]
) -> list[schemas.QueueInDb]:
return [schemas.QueueInDb.model_validate(q) for q in current_user.owns_queues]
def get_user_queues(
current_user: Annotated[auth_schemas.User, Depends(auth_services.get_anon_user)]
) -> list[schemas.QueueInDb]:
return [
schemas.QueueInDb.model_validate(q.queue)
for q in current_user.parts_in_queues.filter(models.QueueUser.passed == False)
]
def create_queue(
new_queue: schemas.Queue,
current_user: auth_schemas.UserInDB,
db: Session,
) -> schemas.QueueInDb:
q = models.Queue(
name=new_queue.name, description=new_queue.description, owner_id=current_user.id
name=new_queue.name,
description=new_queue.description,
owner_id=current_user.id,
status="created",
)
db.add(q)
db.commit()
@ -48,6 +62,8 @@ def get_detailed_queue(
id=q.id,
name=q.name,
description=q.description,
status=q.status,
owner_id=q.owner_id,
participants=schemas.ParticipantInfo(
total=q.users.count(),
remaining=q.users.filter(models.QueueUser.passed == False).count(),
@ -102,3 +118,132 @@ async def set_queue_listener(
await ps.unsubscribe()
new_queue = get_detailed_queue(queue_id=queue_id, db=db)
return new_queue
async def get_queue_owner(
queue: Annotated[models.Queue, Depends(get_queue_by_id)]
) -> auth_schemas.UserInDB:
return queue.owner if queue else None
async def verify_queue_owner(
queue_owner: Annotated[auth_schemas.UserInDB, Depends(get_queue_owner)],
current_user: Annotated[
auth_schemas.UserInDB, Depends(auth_services.get_current_user)
],
) -> bool:
return queue_owner.id == current_user.id if queue_owner and current_user else False
async def rebuild_queue(
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
db: Annotated[Session, Depends(get_db)],
):
for i, qu in enumerate(
queue.users.filter(models.QueueUser.passed == False).order_by(
models.QueueUser.position.asc()
)
):
setattr(qu, "position", i)
db.commit()
async def kick_first(
is_owner: Annotated[bool, Depends(verify_queue_owner)],
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
db: Annotated[Session, Depends(get_db)],
):
if is_owner:
first_user = (
queue.users.filter(models.QueueUser.passed == False)
.order_by(models.QueueUser.position.asc())
.first()
)
if first_user:
setattr(first_user, "passed", True)
db.commit()
await rebuild_queue(queue=queue, db=db)
return
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No first user",
)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="You are not a queue owner!",
)
async def start_queue(
is_owner: Annotated[bool, Depends(verify_queue_owner)],
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
db: Annotated[Session, Depends(get_db)],
):
if queue and is_owner:
setattr(queue, "status", "active")
db.commit()
await rebuild_queue(queue=queue, db=db)
return
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="You are not a queue owner!",
)
async def pass_queueuser(
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
anon_user: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
db: Annotated[Session, Depends(get_db)],
):
if anon_user:
qu = (
db.query(models.QueueUser)
.filter(
models.QueueUser.queue_id == queue.id,
models.QueueUser.user_id == anon_user.id,
)
.first()
)
if qu:
setattr(qu, "passed", True)
db.commit()
await rebuild_queue(queue=queue, db=db)
return
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found!",
)
async def action_wrapper(
action: str,
is_owner: Annotated[bool, Depends(verify_queue_owner)],
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
anon_user: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
db: Annotated[Session, Depends(get_db)],
r: Annotated[redis.client.Redis, Depends(get_redis)],
) -> schemas.ActionResult:
if queue:
if action == "kick-first":
await kick_first(is_owner=is_owner, queue=queue, db=db)
await r.publish(str(queue.id), "updated")
return {"action": action, "status": "success"}
if action == "pass":
await pass_queueuser(queue=queue, anon_user=anon_user, db=db)
await r.publish(str(queue.id), "updated")
return {"action": action, "status": "success"}
if action == "start":
await start_queue(queue=queue, is_owner=is_owner, db=db)
await r.publish(str(queue.id), "updated")
return {"action": action, "status": "success"}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Action not found!",
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Queue not found!",
)