diff --git a/api/app/application/sessions_repository.py b/api/app/application/sessions_repository.py
index c3a34af..e1a1bb5 100644
--- a/api/app/application/sessions_repository.py
+++ b/api/app/application/sessions_repository.py
@@ -22,7 +22,7 @@ class SessionsRepository:
result = await self.db.execute(
select(Session).filter_by(id=session_id)
)
- return result.scalars().all()
+ return result.scalars().first()
async def get_by_token(self, token: str) -> Optional[Session]:
result = await self.db.execute(
diff --git a/api/app/infrastructure/auth_service.py b/api/app/infrastructure/auth_service.py
index 9be0f83..2f46302 100644
--- a/api/app/infrastructure/auth_service.py
+++ b/api/app/infrastructure/auth_service.py
@@ -22,7 +22,7 @@ class AuthService:
user = await self.users_repository.get_by_login(login)
if user and user.check_password(password):
access_token = self.create_access_token({"user_id": user.id})
- # Создаем сессию
+
session = Session(
user_id=user.id,
token=access_token,
diff --git a/web-app/src/Api/authApi.js b/web-app/src/Api/authApi.js
index 27ef36b..0b7b0c3 100644
--- a/web-app/src/Api/authApi.js
+++ b/web-app/src/Api/authApi.js
@@ -1,7 +1,6 @@
import {createApi} from "@reduxjs/toolkit/query/react";
import {baseQueryWithAuth} from "./baseQuery.js";
-
export const authApi = createApi({
reducerPath: "authApi",
baseQuery: baseQueryWithAuth,
@@ -13,7 +12,25 @@ export const authApi = createApi({
body: credentials,
}),
}),
+ getSessions: builder.query({
+ query: () => ({
+ url: "/sessions/",
+ method: "GET",
+ }),
+ }),
+ logoutSession: builder.mutation({
+ query: (sessionId) => ({
+ url: `/sessions/${sessionId}/logout/`,
+ method: "POST",
+ }),
+ }),
+ logoutAllSessions: builder.mutation({
+ query: () => ({
+ url: "/sessions/logout_all/",
+ method: "POST",
+ }),
+ }),
}),
});
-export const {useLoginMutation} = authApi;
\ No newline at end of file
+export const {useLoginMutation, useGetSessionsQuery, useLogoutSessionMutation, useLogoutAllSessionsMutation} = authApi;
\ No newline at end of file
diff --git a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx
index e61f1f1..e4344f4 100644
--- a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx
+++ b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx
@@ -1,11 +1,12 @@
-import {Button, Card, Col, Row, Typography, Result} from "antd";
+import {Button, Card, Col, Row, Typography, Result, List, Space} from "antd";
import {EditOutlined, UserOutlined} from "@ant-design/icons";
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
import useProfilePage from "./useProfilePage.js";
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
-const ProfilePage = () => {
+const {Title, Text} = Typography;
+const ProfilePage = () => {
const {
containerStyle,
cardStyle,
@@ -13,7 +14,12 @@ const ProfilePage = () => {
isLoading,
isError,
userData,
+ sessions,
handleEditUser,
+ handleLogoutSession,
+ handleLogoutAllSessions,
+ isLoggingOutSession,
+ isLoggingOutAll,
} = useProfilePage();
if (isError) {
@@ -21,7 +27,7 @@ const ProfilePage = () => {
);
}
@@ -32,27 +38,31 @@ const ProfilePage = () => {
) : (
<>
-
+
{userData.last_name} {userData.first_name}
-
- Профиль
-
+
+ Профиль
+
- Фамилия:
- {userData.last_name || "-"}
+ Фамилия:
+ {userData.last_name || "-"}
- Имя:
- {userData.first_name || "-"}
+ Имя:
+ {userData.first_name || "-"}
- Отчество:
- {userData.patronymic || "-"}
+ Отчество:
+ {userData.patronymic || "-"}
- Логин:
- {userData.login || "-"}
+ Логин:
+ {userData.login || "-"}
+
+
+ Роль:
+ {userData.role?.title || "-"}
+
+ (
+ handleLogoutSession(session.id)}
+ loading={isLoggingOutSession}
+ disabled={isLoggingOutSession}
+ >
+ Завершить
+ ,
+ ]}
+ >
+
+
+ )}
+ />
+
+
+
+
+
>
)}
diff --git a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js
index 630a955..9cac43a 100644
--- a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js
+++ b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js
@@ -1,40 +1,95 @@
import {Grid} from "antd";
-import {useDispatch} from "react-redux";
+import {useDispatch, useSelector} from "react-redux";
+import {useNavigate} from "react-router-dom";
import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
+import {useGetSessionsQuery, useLogoutSessionMutation, useLogoutAllSessionsMutation} from "../../../Api/authApi.js";
+import {logout, setError} from "../../../Redux/Slices/authSlice.js";
const {useBreakpoint} = Grid;
const useProfilePage = () => {
const dispatch = useDispatch();
+ const navigate = useNavigate();
const screens = useBreakpoint();
+ const {user} = useSelector((state) => state.auth);
const {
data: userData = {},
isLoading: isLoadingUserData,
isError: isErrorUserData,
} = useGetAuthenticatedUserDataQuery(undefined, {
+ pollingInterval: 10000,
+ });
+
+ const {
+ data: sessions = [],
+ isLoading: isLoadingSessions,
+ isError: isErrorSessions,
+ error: sessionsError,
+ } = useGetSessionsQuery(undefined, {
+ skip: !user,
pollingInterval: 20000,
});
+ const [logoutSession, {isLoading: isLoggingOutSession}] = useLogoutSessionMutation();
+ const [logoutAllSessions, {isLoading: isLoggingOutAll}] = useLogoutAllSessionsMutation();
+
const containerStyle = {padding: screens.xs ? 16 : 24};
const cardStyle = {marginBottom: 24};
const buttonStyle = {width: screens.xs ? "100%" : "auto"};
-
const handleEditUser = () => {
- dispatch(setSelectedUser(userData))
+ dispatch(setSelectedUser(userData));
};
+ const handleLogoutSession = async (sessionId) => {
+ try {
+ await logoutSession(sessionId).unwrap();
+ } catch (error) {
+ const errorMessage = error?.data?.detail || "Не удалось завершить сессию";
+ dispatch(setError(errorMessage));
+ if (error?.status === 401) {
+ dispatch(logout());
+ navigate("/login");
+ }
+ }
+ };
+
+ const handleLogoutAllSessions = async () => {
+ try {
+ await logoutAllSessions().unwrap();
+ dispatch(logout());
+ navigate("/login");
+ } catch (error) {
+ const errorMessage = error?.data?.detail || "Не удалось завершить все сессии";
+ dispatch(setError(errorMessage));
+ if (error?.status === 401) {
+ dispatch(logout());
+ navigate("/login");
+ }
+ }
+ };
+
+ if (isErrorSessions && sessionsError?.status === 401) {
+ dispatch(logout());
+ navigate("/login");
+ }
+
return {
userData,
+ sessions,
containerStyle,
cardStyle,
buttonStyle,
isMobile: screens.xs,
- isLoading: isLoadingUserData,
- isError: isErrorUserData,
+ isLoading: isLoadingUserData || isLoadingSessions,
+ isError: isErrorUserData || isErrorSessions,
handleEditUser,
+ handleLogoutSession,
+ handleLogoutAllSessions,
+ isLoggingOutSession,
+ isLoggingOutAll,
};
};