finally, queue logic

This commit is contained in:
2024-04-14 01:19:20 +03:00
parent d716a92dac
commit c64958bb9d
15 changed files with 177 additions and 17 deletions

View File

@ -1,4 +1,4 @@
from .secret import SECRET_KEY
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
ACCESS_TOKEN_EXPIRE_WEEKS = 2

View File

@ -34,11 +34,35 @@ class AnonymousUser(Base):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String, index=True)
parts_in_queues = relationship("QueueUser", backref="user", lazy="dynamic")
class Queue(Base):
__tablename__ = "queues"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
created = Column(DateTime, default=datetime.datetime.utcnow)
name = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
start_time = Column(DateTime, nullable=True)
users = relationship("QueueUser", backref="queue", lazy="dynamic")
class QueueUser(Base):
__tablename__ = "queueuser"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("anonymoususers.id"))
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
position = Column(Integer)
passed = Column(Boolean, default=False)
class QueueLog(Base):
__tablename__ = "queuelog"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
action = Column(String)
created = Column(DateTime, default=datetime.datetime.utcnow)

View File

@ -34,7 +34,7 @@ async def login_for_access_token(
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=jwt_config.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token_expires = timedelta(weeks=jwt_config.ACCESS_TOKEN_EXPIRE_WEEKS)
access_token = services.create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)

View File

@ -46,7 +46,7 @@ def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
expire = datetime.now(timezone.utc) + timedelta(weeks=2)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode, jwt_config.SECRET_KEY, algorithm=jwt_config.ALGORITHM
@ -90,6 +90,24 @@ async def get_current_user(
return user
async def get_current_user_or_none(
token: Annotated[str, Depends(oauth2_scheme)],
db: Annotated[Session, Depends(get_db)],
) -> Union[schemas.UserInDB, None]:
try:
payload = jwt.decode(
token, jwt_config.SECRET_KEY, algorithms=[jwt_config.ALGORITHM]
)
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = schemas.TokenData(username=username)
except JWTError:
return None
user = get_user_by_username(db, username=token_data.username)
return user
async def get_current_active_user(
current_user: Annotated[schemas.User, Depends(get_current_user)],
):

View File

@ -32,6 +32,13 @@ async def user_queues_list(
return queues
@router.get("/{queue_id}")
async def user_queues_list(
queue: Annotated[schemas.QueueDetail, Depends(services.get_detailed_queue)],
) -> schemas.QueueDetail:
return queue
@router.post("/")
async def create_queue(
new_queue: schemas.Queue,

View File

@ -25,3 +25,8 @@ class QueueInDb(Queue):
class Config:
from_attributes = True
class QueueDetail(Queue):
id: UUID
participants: ParticipantInfo

View File

@ -1,6 +1,7 @@
from fastapi import Depends
from fastapi import Depends, HTTPException, status
from typing import Annotated
from sqlalchemy.orm import Session
from uuid import UUID
from ...dependencies import get_db
from ...db import models
@ -28,3 +29,25 @@ def create_queue(
db.add(q)
db.commit()
return schemas.QueueInDb.model_validate(q)
def get_detailed_queue(
queue_id: UUID,
db: Annotated[Session, Depends(get_db)],
) -> schemas.QueueDetail:
q = db.query(models.Queue).filter(models.Queue.id == queue_id).first()
print("\n\n", queue_id, "\n\n", flush=True)
if q:
return schemas.QueueDetail(
id=q.id,
name=q.name,
description=q.description,
participants=schemas.ParticipantInfo(
total=q.users.count(),
remaining=q.users.filter(models.QueueUser.passed == False).count(),
),
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Not Found",
)

View File

@ -7,6 +7,7 @@ import { MessageContext } from "../../App";
import { useNavigate } from "react-router-dom";
import { PlusCircleOutlined } from "@ant-design/icons";
import { CreateNewsRequest, useCreateNewsMutation } from "../../slice/NewsApi";
import TextArea from "antd/es/input/TextArea";
const CreateNewsCard = (): JSX.Element => {
const messageApi = useContext(MessageContext);
@ -55,7 +56,7 @@ const CreateNewsCard = (): JSX.Element => {
},
]}
>
<Input />
<TextArea />
</Form.Item>
<Button
style={{ width: "100%" }}

View File

@ -22,7 +22,13 @@ const NewsListCard = (): JSX.Element => {
style={{ width: "100%", marginBottom: "1rem" }}
bordered={false}
>
<p style={{ textAlign: "left", color: "white" }}>
<p
style={{
textAlign: "left",
color: "#ffffff",
whiteSpace: "break-spaces",
}}
>
{news.content}
</p>
<br />

View File

@ -22,7 +22,7 @@ const CreateQueueCard = (): JSX.Element => {
const submit = (formData: CreateQueueRequest) => {
createQueue(formData)
.unwrap()
.then((data: Queue) => navigate(`/dashboard/queue/${data.id}`))
.then((data: Queue) => navigate(`/queue/${data.id}`))
.then(() => messageApi.success(tr("Queue created")))
.catch(() => messageApi.error(tr("Failed to create queue")));
};

View File

@ -0,0 +1,50 @@
import React, { useEffect } from "react";
import {
useGetQueueDetailQuery,
useGetQueuesQuery,
} from "../../slice/QueueApi";
import "../styles.css";
import { Button, Spin } from "antd";
import {
ArrowUpOutlined,
FileTextOutlined,
LoadingOutlined,
PlusCircleOutlined,
UserOutlined,
} from "@ant-design/icons";
import Title from "antd/es/typography/Title";
import tr from "../../config/translation";
import { Link, useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { StorePrototype } from "../../config/store";
const QueueCard = (): JSX.Element => {
const user = useSelector((state: StorePrototype) => state.auth.user);
const { queueId } = useParams();
const { data, isFetching } = useGetQueueDetailQuery(queueId);
return (
<div className="card">
<Spin
indicator={<LoadingOutlined style={{ fontSize: 36 }} spin />}
spinning={isFetching}
>
<div className="queue-info">
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
</div>
</Spin>
</div>
);
};
export default QueueCard;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import { useGetQueuesQuery } from "../../slice/QueueApi";
import "../styles.css";
import { Button, Spin } from "antd";
@ -6,10 +6,13 @@ import {
ArrowUpOutlined,
LoadingOutlined,
PlusCircleOutlined,
RightOutlined,
} from "@ant-design/icons";
import Title from "antd/es/typography/Title";
import tr from "../../config/translation";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
import { StorePrototype } from "../../config/store";
type Queue = {
id: string;
@ -17,19 +20,21 @@ type Queue = {
};
const QueuesList = (): JSX.Element => {
const { data, isLoading } = useGetQueuesQuery({});
const user = useSelector((state: StorePrototype) => state.auth.user);
const { data, refetch, isLoading } = useGetQueuesQuery({});
useEffect(() => {
user && refetch();
}, [user]);
return (
<div className="card">
<Title level={2}>{tr("My queues")}</Title>
<Link to="/dashboard/new">
<Button
type="primary"
style={{ width: "100%" }}
icon={<PlusCircleOutlined />}
>
<Button type="primary" icon={<PlusCircleOutlined />}>
{tr("Create queue")}
</Button>
</Link>
<Title level={2}>{tr("My queues")}</Title>
<br />
<br />
<Spin
indicator={<LoadingOutlined style={{ fontSize: 36 }} spin />}
spinning={isLoading}
@ -38,6 +43,14 @@ const QueuesList = (): JSX.Element => {
data?.map((ele: Queue) => (
<div className="card secondary queue-in-list" key={ele.id}>
<Title level={4}>{ele.name}</Title>
<Link to={`/queue/${ele.id}`}>
<Button
icon={<RightOutlined />}
type="primary"
size="large"
style={{ height: "100%", width: "4rem" }}
/>
</Link>
</div>
))
) : (

View File

@ -41,9 +41,9 @@
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
margin-top: 1rem;
margin-bottom: 1rem;
border: 2px solid #00d8a4;
}
@keyframes headerDrop {
@ -97,3 +97,7 @@
display: flex;
flex-flow: row;
}
.queue-info > * {
text-align: left;
}

View File

@ -8,6 +8,7 @@ import NotFoundPage from "./NotFoundPage";
import NewQueuePage from "./NewQueuePage";
import NewsPage from "./NewsPage";
import CreateNewsPage from "./CreateNewsPage";
import QueueCard from "../components/queue/QueueCard";
const AppRoutes = ({ children }: { children: ReactNode }) => {
store.dispatch(getLocalToken());
@ -19,6 +20,7 @@ const AppRoutes = ({ children }: { children: ReactNode }) => {
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/queue/:queueId" element={<QueueCard />} />
<Route path="/dashboard/new" element={<NewQueuePage />} />
<Route path="/news" element={<NewsPage />} />
<Route path="/news/new" element={<CreateNewsPage />} />

View File

@ -29,6 +29,9 @@ export const QueueApi = createApi({
getQueues: builder.query({
query: () => "/",
}),
getQueueDetail: builder.query({
query: (queueId: string | undefined) => `/${queueId}`,
}),
createQueue: builder.mutation({
query: (data: CreateQueueRequest) => ({
url: "/",
@ -39,4 +42,8 @@ export const QueueApi = createApi({
}),
});
export const { useGetQueuesQuery, useCreateQueueMutation } = QueueApi;
export const {
useGetQueuesQuery,
useGetQueueDetailQuery,
useCreateQueueMutation,
} = QueueApi;