news & things
This commit is contained in:
@ -1,7 +1,8 @@
|
|||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
import uuid
|
import uuid
|
||||||
|
import datetime
|
||||||
|
|
||||||
from .database import Base
|
from .database import Base
|
||||||
|
|
||||||
@ -18,6 +19,15 @@ class User(Base):
|
|||||||
owns_queues = relationship("Queue", backref="owner", lazy="dynamic")
|
owns_queues = relationship("Queue", backref="owner", lazy="dynamic")
|
||||||
|
|
||||||
|
|
||||||
|
class News(Base):
|
||||||
|
__tablename__ = "news"
|
||||||
|
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
title = Column(String)
|
||||||
|
content = Column(String)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(Base):
|
class AnonymousUser(Base):
|
||||||
__tablename__ = "anonymoususers"
|
__tablename__ = "anonymoususers"
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,14 @@ from .dependencies import get_db
|
|||||||
|
|
||||||
from .views.auth.api import router as auth_router
|
from .views.auth.api import router as auth_router
|
||||||
from .views.queue.api import router as queue_router
|
from .views.queue.api import router as queue_router
|
||||||
|
from .views.news.api import router as news_router
|
||||||
|
|
||||||
app = FastAPI(dependencies=[Depends(get_db)])
|
app = FastAPI(dependencies=[Depends(get_db)])
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app.include_router(queue_router)
|
app.include_router(queue_router)
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
|
app.include_router(news_router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
0
backend/app/views/news/__init__.py
Normal file
0
backend/app/views/news/__init__.py
Normal file
41
backend/app/views/news/api.py
Normal file
41
backend/app/views/news/api.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from typing import Annotated, Union
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from ...config import jwt_config
|
||||||
|
from ...dependencies import get_db
|
||||||
|
from . import schemas
|
||||||
|
from . import services
|
||||||
|
|
||||||
|
from ..auth import services as auth_services
|
||||||
|
from ..auth import schemas as auth_schemas
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/news",
|
||||||
|
tags=["news"],
|
||||||
|
dependencies=[Depends(get_db)],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
async def get_news(
|
||||||
|
news: Annotated[schemas.NewsInDb, Depends(services.get_news)],
|
||||||
|
) -> list[schemas.NewsInDb]:
|
||||||
|
return news
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
async def create_news(
|
||||||
|
news: schemas.CreateNews,
|
||||||
|
current_user: Annotated[auth_schemas.User, Depends(auth_services.get_current_user)],
|
||||||
|
db: Annotated[Session, Depends(get_db)],
|
||||||
|
) -> schemas.NewsInDb:
|
||||||
|
return services.create_news(news=news, current_user=current_user, db=db)
|
||||||
22
backend/app/views/news/schemas.py
Normal file
22
backend/app/views/news/schemas.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from typing import Union
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class CreateNews(BaseModel):
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
class News(BaseModel):
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
created: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class NewsInDb(News):
|
||||||
|
id: UUID
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
37
backend/app/views/news/services.py
Normal file
37
backend/app/views/news/services.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from fastapi import Depends, HTTPException
|
||||||
|
from typing import Annotated
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from ...dependencies import get_db
|
||||||
|
from ...db import models
|
||||||
|
|
||||||
|
from ..auth import services as auth_services
|
||||||
|
from ..auth import schemas as auth_schemas
|
||||||
|
|
||||||
|
from . import schemas
|
||||||
|
|
||||||
|
|
||||||
|
def get_news(
|
||||||
|
db: Annotated[Session, Depends(get_db)],
|
||||||
|
) -> list[schemas.NewsInDb]:
|
||||||
|
return [
|
||||||
|
schemas.NewsInDb.model_validate(n)
|
||||||
|
for n in db.query(models.News).order_by(models.News.created.desc()).all()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_news(
|
||||||
|
news: schemas.CreateNews,
|
||||||
|
current_user: auth_schemas.UserInDB,
|
||||||
|
db: Session,
|
||||||
|
) -> schemas.NewsInDb:
|
||||||
|
if current_user.username == "admin":
|
||||||
|
n = models.News(title=news.title, content=news.content)
|
||||||
|
db.add(n)
|
||||||
|
db.commit()
|
||||||
|
return schemas.NewsInDb.model_validate(n)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
@ -19,6 +19,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
const AuthModal = (props: {
|
const AuthModal = (props: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (arg0: boolean) => void;
|
setOpen: (arg0: boolean) => void;
|
||||||
|
setDrawerOpen: (arg0: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const messageApi = useContext(MessageContext);
|
const messageApi = useContext(MessageContext);
|
||||||
@ -58,6 +59,7 @@ const AuthModal = (props: {
|
|||||||
.then(() => refetch())
|
.then(() => refetch())
|
||||||
.then(() => props.setOpen(false))
|
.then(() => props.setOpen(false))
|
||||||
.then(() => navigate("/dashboard"))
|
.then(() => navigate("/dashboard"))
|
||||||
|
.then(() => props.setDrawerOpen(false))
|
||||||
.catch(() => messageApi.error(tr("Login failed!")));
|
.catch(() => messageApi.error(tr("Login failed!")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
GlobalOutlined,
|
GlobalOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
MenuOutlined,
|
MenuOutlined,
|
||||||
|
PicCenterOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
@ -100,6 +101,11 @@ const HeaderComponent = () => {
|
|||||||
icon: <DesktopOutlined />,
|
icon: <DesktopOutlined />,
|
||||||
disabled: !user,
|
disabled: !user,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: <Link to="/news">{tr("News")}</Link>,
|
||||||
|
key: "news",
|
||||||
|
icon: <PicCenterOutlined />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: tr("Language"),
|
label: tr("Language"),
|
||||||
key: "language",
|
key: "language",
|
||||||
@ -111,7 +117,7 @@ const HeaderComponent = () => {
|
|||||||
label: user ? user.username : tr("Log in"),
|
label: user ? user.username : tr("Log in"),
|
||||||
key: "login",
|
key: "login",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
onClick: () => setAuthModalOpen(true),
|
onClick: () => !user && setAuthModalOpen(true),
|
||||||
...(user ? { children: userMenuItems } : {}),
|
...(user ? { children: userMenuItems } : {}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -139,6 +145,13 @@ const HeaderComponent = () => {
|
|||||||
key: "dashboard",
|
key: "dashboard",
|
||||||
icon: <DesktopOutlined />,
|
icon: <DesktopOutlined />,
|
||||||
disabled: !user,
|
disabled: !user,
|
||||||
|
onClick: () => setDrawerOpen(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: <Link to="/news">{tr("News")}</Link>,
|
||||||
|
key: "news",
|
||||||
|
icon: <PicCenterOutlined />,
|
||||||
|
onClick: () => setDrawerOpen(false),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: tr("Language"),
|
label: tr("Language"),
|
||||||
@ -151,14 +164,18 @@ const HeaderComponent = () => {
|
|||||||
label: user ? user.username : tr("Log in"),
|
label: user ? user.username : tr("Log in"),
|
||||||
key: "login",
|
key: "login",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
onClick: () => setAuthModalOpen(true),
|
onClick: () => !user && setAuthModalOpen(true),
|
||||||
...(user ? { children: userMenuItems } : {}),
|
...(user ? { children: userMenuItems } : {}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AuthModal open={authModalOpen} setOpen={setAuthModalOpen} />
|
<AuthModal
|
||||||
|
open={authModalOpen}
|
||||||
|
setOpen={setAuthModalOpen}
|
||||||
|
setDrawerOpen={setDrawerOpen}
|
||||||
|
/>
|
||||||
<Header className="header">
|
<Header className="header">
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
theme="dark"
|
||||||
|
|||||||
73
frontend/app/src/components/news/CreateNewsCard.tsx
Normal file
73
frontend/app/src/components/news/CreateNewsCard.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { useContext } from "react";
|
||||||
|
import "../styles.css";
|
||||||
|
import { Button, Form, Input, Spin } from "antd";
|
||||||
|
import Title from "antd/es/typography/Title";
|
||||||
|
import tr from "../../config/translation";
|
||||||
|
import { MessageContext } from "../../App";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||||
|
import { CreateNewsRequest, useCreateNewsMutation } from "../../slice/NewsApi";
|
||||||
|
|
||||||
|
const CreateNewsCard = (): JSX.Element => {
|
||||||
|
const messageApi = useContext(MessageContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [createNews, { isLoading }] = useCreateNewsMutation();
|
||||||
|
|
||||||
|
const submit = (formData: CreateNewsRequest) => {
|
||||||
|
createNews(formData)
|
||||||
|
.unwrap()
|
||||||
|
.then(() => navigate(`/news`))
|
||||||
|
.then(() => messageApi.success(tr("News created")))
|
||||||
|
.catch(() => messageApi.error(tr("Failed to create news")));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<Spin spinning={isLoading}>
|
||||||
|
<Title level={2}>{tr("New news")}</Title>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
requiredMark={false}
|
||||||
|
onFinish={(formData: CreateNewsRequest) => submit(formData)}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name={"title"}
|
||||||
|
label={tr("Title")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: tr("Please input news title!"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={"content"}
|
||||||
|
label={tr("Content")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: tr("Please input news content!"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
icon={<PlusCircleOutlined />}
|
||||||
|
type="primary"
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
>
|
||||||
|
{tr("Publish")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CreateNewsCard;
|
||||||
40
frontend/app/src/components/news/NewsListCard.tsx
Normal file
40
frontend/app/src/components/news/NewsListCard.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "../styles.css";
|
||||||
|
import { Card, Spin } from "antd";
|
||||||
|
import { News, useGetNewsQuery } from "../../slice/NewsApi";
|
||||||
|
import { ClockCircleOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
const formatTime = (s: string) => {
|
||||||
|
const d = new Date(s);
|
||||||
|
return d.toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewsListCard = (): JSX.Element => {
|
||||||
|
const { data, isLoading } = useGetNewsQuery({});
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<Spin spinning={isLoading}>
|
||||||
|
{data &&
|
||||||
|
data.map((news: News) => (
|
||||||
|
<Card
|
||||||
|
title={news.title}
|
||||||
|
key={news.id}
|
||||||
|
style={{ width: "100%", marginBottom: "1rem" }}
|
||||||
|
bordered={false}
|
||||||
|
>
|
||||||
|
<p style={{ textAlign: "left", color: "white" }}>
|
||||||
|
{news.content}
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<div className="news-footer">
|
||||||
|
<ClockCircleOutlined />
|
||||||
|
<br />
|
||||||
|
<p style={{ marginLeft: "1rem" }}>{formatTime(news.created)}</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default NewsListCard;
|
||||||
@ -91,3 +91,9 @@
|
|||||||
transform: rotate(0.05turn);
|
transform: rotate(0.05turn);
|
||||||
transition-duration: 0.2s;
|
transition-duration: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.news-footer {
|
||||||
|
color: grey;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
import { AuthApi, User } from "../slice/AuthApi";
|
import { AuthApi, User } from "../slice/AuthApi";
|
||||||
import { QueueApi } from "../slice/QueueApi";
|
import { QueueApi } from "../slice/QueueApi";
|
||||||
|
import { NewsApi } from "../slice/NewsApi";
|
||||||
|
|
||||||
export type AuthDataType = {
|
export type AuthDataType = {
|
||||||
token: string | null;
|
token: string | null;
|
||||||
@ -45,6 +46,7 @@ export const store = configureStore({
|
|||||||
// Add the generated reducer as a specific top-level slice
|
// Add the generated reducer as a specific top-level slice
|
||||||
[AuthApi.reducerPath]: AuthApi.reducer,
|
[AuthApi.reducerPath]: AuthApi.reducer,
|
||||||
[QueueApi.reducerPath]: QueueApi.reducer,
|
[QueueApi.reducerPath]: QueueApi.reducer,
|
||||||
|
[NewsApi.reducerPath]: NewsApi.reducer,
|
||||||
auth: createReducer(initialAuthDataState, (builder) => {
|
auth: createReducer(initialAuthDataState, (builder) => {
|
||||||
builder.addCase(updateToken, (state, action) => {
|
builder.addCase(updateToken, (state, action) => {
|
||||||
state.token = action.payload;
|
state.token = action.payload;
|
||||||
@ -85,7 +87,8 @@ export const store = configureStore({
|
|||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware()
|
getDefaultMiddleware()
|
||||||
.concat(AuthApi.middleware)
|
.concat(AuthApi.middleware)
|
||||||
.concat(QueueApi.middleware),
|
.concat(QueueApi.middleware)
|
||||||
|
.concat(NewsApi.middleware),
|
||||||
});
|
});
|
||||||
|
|
||||||
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
|
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
|
||||||
|
|||||||
@ -94,5 +94,8 @@
|
|||||||
},
|
},
|
||||||
"Failed to create queue": {
|
"Failed to create queue": {
|
||||||
"ru": "Не удалось создать очередь"
|
"ru": "Не удалось создать очередь"
|
||||||
|
},
|
||||||
|
"News": {
|
||||||
|
"ru": "Новости"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,6 +6,8 @@ import DashboardPage from "./DashboardPage";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import NotFoundPage from "./NotFoundPage";
|
import NotFoundPage from "./NotFoundPage";
|
||||||
import NewQueuePage from "./NewQueuePage";
|
import NewQueuePage from "./NewQueuePage";
|
||||||
|
import NewsPage from "./NewsPage";
|
||||||
|
import CreateNewsPage from "./CreateNewsPage";
|
||||||
|
|
||||||
const AppRoutes = ({ children }: { children: ReactNode }) => {
|
const AppRoutes = ({ children }: { children: ReactNode }) => {
|
||||||
store.dispatch(getLocalToken());
|
store.dispatch(getLocalToken());
|
||||||
@ -18,6 +20,8 @@ const AppRoutes = ({ children }: { children: ReactNode }) => {
|
|||||||
<Route path="/" element={<MainPage />} />
|
<Route path="/" element={<MainPage />} />
|
||||||
<Route path="/dashboard" element={<DashboardPage />} />
|
<Route path="/dashboard" element={<DashboardPage />} />
|
||||||
<Route path="/dashboard/new" element={<NewQueuePage />} />
|
<Route path="/dashboard/new" element={<NewQueuePage />} />
|
||||||
|
<Route path="/news" element={<NewsPage />} />
|
||||||
|
<Route path="/news/new" element={<CreateNewsPage />} />
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
17
frontend/app/src/pages/CreateNewsPage.tsx
Normal file
17
frontend/app/src/pages/CreateNewsPage.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "./styles.css";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { StorePrototype } from "../config/store";
|
||||||
|
import CreateNewsCard from "../components/news/CreateNewsCard";
|
||||||
|
import NotFoundPage from "./NotFoundPage";
|
||||||
|
|
||||||
|
const CreateNewsPage = () => {
|
||||||
|
const user = useSelector((state: StorePrototype) => state.auth.user);
|
||||||
|
return user && user.username === "admin" ? (
|
||||||
|
<CreateNewsCard />
|
||||||
|
) : (
|
||||||
|
<NotFoundPage />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateNewsPage;
|
||||||
9
frontend/app/src/pages/NewsPage.tsx
Normal file
9
frontend/app/src/pages/NewsPage.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "./styles.css";
|
||||||
|
import NewsListCard from "../components/news/NewsListCard";
|
||||||
|
|
||||||
|
const NewsPage = () => {
|
||||||
|
return <NewsListCard />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewsPage;
|
||||||
43
frontend/app/src/slice/NewsApi.ts
Normal file
43
frontend/app/src/slice/NewsApi.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||||
|
import { baseUrl } from "../config/baseUrl";
|
||||||
|
import { RootState } from "../config/store";
|
||||||
|
|
||||||
|
export type CreateNewsRequest = {
|
||||||
|
title: string;
|
||||||
|
content: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type News = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
created: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewsApi = createApi({
|
||||||
|
reducerPath: "NewsApi",
|
||||||
|
baseQuery: fetchBaseQuery({
|
||||||
|
baseUrl: `${baseUrl}/news`,
|
||||||
|
prepareHeaders: (headers, { getState }) => {
|
||||||
|
const token = (getState() as RootState).auth.token;
|
||||||
|
if (token) {
|
||||||
|
headers.set("authorization", `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getNews: builder.query({
|
||||||
|
query: () => "/",
|
||||||
|
}),
|
||||||
|
createNews: builder.mutation({
|
||||||
|
query: (data: CreateNewsRequest) => ({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { useGetNewsQuery, useCreateNewsMutation } = NewsApi;
|
||||||
Reference in New Issue
Block a user