Compare commits

...

57 Commits

Author SHA1 Message Date
6d3657f26c Правка БД. 2023-05-26 19:51:51 +04:00
1875dcd30d Отчёт. 2023-05-26 19:46:09 +04:00
f27cacb884 Отчёт. 2023-05-26 19:42:06 +04:00
fef50dbb5c LabWork06, уююююю :)))
Но если для души делать, то для доп. метода допилить запросы и убрать при заходе от клиента кнопки со страниц
2023-05-16 13:57:35 +04:00
74625c1a88 Настроил стили и ещё по мелочи :) 2023-05-16 12:23:29 +04:00
949716b6b2 Вроде как все CRUD для nation.. 2023-05-16 02:39:01 +04:00
2f0e1edba8 Всё ближе, и ближе, и блииижеееее 2023-05-16 02:27:55 +04:00
b3f016eca3 Вновь небольшое продвижение. 2023-05-16 00:40:16 +04:00
53911bccf8 Постепенно идём к истине 2023-05-15 22:38:15 +04:00
1820b567a6 На беке пока типо всё. Но это только пока... 2023-05-15 15:59:55 +04:00
92c7edb8f2 Добавили новый implement. 2023-05-15 15:43:28 +04:00
13b10d97ac Начало работы с React. 2023-05-15 15:29:55 +04:00
537fffe177 -+-+- 2023-05-15 15:05:40 +04:00
d1b53d51b6 На всякий случай. MVC почти доделан!!! 2023-05-15 12:24:26 +04:00
8803176103 Another fixation. 2023-05-15 00:18:18 +04:00
1ba8b942ea Another fixation. 2023-05-14 12:26:37 +04:00
1205d4125f Продолжаем правки по Алексею Александровичу. 2023-05-13 11:32:16 +04:00
6a83584f50 Начинаем правки по примеру с LMS. 2023-05-13 10:49:24 +04:00
65800b9a6f Сохранение перед перезагрузкой. 2023-05-13 08:57:19 +04:00
68a0bd7c14 Тут дальше трам-пам-пам полный 2023-05-12 23:28:38 +04:00
cefa4a0c02 Another fixation :) 2023-05-12 23:15:46 +04:00
c3577d6ad5 Вновь дела делаются. 2023-05-12 20:41:41 +04:00
42e4e9c1cc Чёт делается. 2023-05-12 19:56:55 +04:00
fc65e71ffa Работа с сущностью Клиент. Добавление поля логин и пароль. 2023-05-12 19:19:37 +04:00
ce4fbf0aeb Ну почти закончил по лекции, но оно 100% не будет работать(( 2023-05-11 20:39:18 +04:00
f735902131 Чё-то делаю... 2023-05-11 20:24:24 +04:00
8c7d51f315 Промежуточное сохранение. 2023-05-11 19:42:44 +04:00
d473c67d59 Ох как намаааялся я с тобой, моя попытка ноомер 4 2023-05-11 12:40:32 +04:00
411dc8319e Ох как намаааялся я с тобой, моя попытка ноомер 3 2023-05-06 12:07:08 +04:00
6d178b58aa Мелкие правки (отключил подсветку th как ошибку). LabWork05. 2023-04-24 16:22:59 +04:00
9b72e39432 LabWork05. 2023-04-24 16:20:28 +04:00
167ca7eb82 Второе глобальное промежуточное сохранение. 2023-04-24 14:10:55 +04:00
843ec51b8c Первое глобальное промежуточное сохранение. 2023-04-24 01:03:24 +04:00
9560588d41 Добавление первых HTML файлов. 2023-04-22 10:50:45 +04:00
b4e48f5c5e Начало 5-й лабораторной. 2023-04-22 10:17:18 +04:00
9f643c860d СЮЮЮЮЮДАААААААААААААА ЭТУ БЕБРОЧКУУУУУУУУУ, О ДААААА 2023-04-19 20:25:07 +04:00
4d76a86c8e А теперь я просто хочу сказать 2023-04-19 20:24:33 +04:00
ce8c5ca468 Четвёртая партия фронта. 2023-04-19 20:23:17 +04:00
15b5ebd034 Третья партия фронта. 2023-04-19 20:22:55 +04:00
81f8008aab Вторая партия фронта. 2023-04-19 20:10:05 +04:00
307acf867a Первая партия фронта. 2023-04-19 20:09:25 +04:00
01470af3bb Точечные правки. Всё работает. 2023-04-19 19:57:09 +04:00
a0ff253478 Добавление DTO. Исправление сервисов и некоторых контроллеров. 2023-04-19 19:44:08 +04:00
973e2c6a16 Правка моделей. 2023-04-19 19:35:44 +04:00
8a23075ac6 Промежуточное сохранение. Надеюсь, что будет работать 2023-04-09 13:41:37 +04:00
43cc05427d Промежуточное сохранение. 2023-04-09 12:05:55 +04:00
e2d99cec86 Переименование сущностей и корректировка тестов. 2023-04-09 11:13:33 +04:00
8ecf9e9cab Добавление классов-контроллеров и DOT. 2023-04-09 09:15:24 +04:00
d0f9fb2bd0 Изменение классов-сервисов. 2023-04-08 23:53:17 +04:00
2844d545ce Начало работ над 4-й лабораторной. 2023-04-08 23:01:18 +04:00
c4e3593fa4 Вроде как рабочий проект для 3-й лабы. 2023-04-08 10:29:58 +04:00
8fa5b5f8c4 Промежуточное сохранение. 2023-04-07 11:15:41 +04:00
663fd6ad72 Добавление всех ревисов. 2023-04-07 11:00:17 +04:00
b79d35d1af Промежуточное сохранение. 2023-04-06 21:31:26 +04:00
9297fb06ae Промежуточное сохранение. 2023-03-27 17:51:24 +04:00
438e1ed4d4 3 готовые сущности. Доработка связи многие ко многим и двух сущностей. 2023-03-27 17:34:32 +04:00
eea6638930 Start 03 lab work 2023-03-27 16:00:40 +04:00
126 changed files with 10583 additions and 5178 deletions

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

19
.idea/compiler.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-configuration-processor/2.6.5/dced3550504fffed49b76972b4c4aed274a623ee/spring-boot-configuration-processor-2.6.5.jar" />
</processorPath>
<module name="premium_store.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="spring_online_calculator" target="17" />
<module name="spring_online_calculator.main" target="17" />
<module name="spring_online_calculator.test" target="17" />
</bytecodeTargetLevel>
</component>
</project>

8
.idea/discord.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="nameOverrideEnabled" value="true" />
<option name="description" value="" />
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$/spring_online_calculator" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/spring_online_calculator" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="th:text" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

20
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

6
.idea/jpa-buddy.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="defaultUnitInitialized" value="true" />
</component>
</project>

8
.idea/misc.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="temurin-17" project-jdk-type="JavaSDK" />
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/С репозитория.iml" filepath="$PROJECT_DIR$/.idea/С репозитория.iml" />
</modules>
</component>
</project>

124
.idea/uiDesigner.xml Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,13 +1,16 @@
{
"name": "online_calculator",
"name": "premuim_store",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5",
"cors": "^2.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="../src/styles/App.css">
<title>Premium store of our games! :)</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.js"></script>
</body>
</html>

View File

@ -0,0 +1,2 @@
VITE_NODE_ENV=development
VITE_API_URL=http://localhost:8080

View File

@ -0,0 +1,60 @@
import { Routes, BrowserRouter, Route } from 'react-router-dom';
import PrivateRoutes from "./components/items/Pages/PrivateRoutes";
import AddLevel from './components/AddLevel';
import AddNation from './components/AddNation';
import AddTank from './components/AddTank';
import AddClient from './components/AddClient';
import PageForChecking from './components/PageForChecking';
import SingupPage from './components/items/Pages/SingupPage';
import LoginPage from './components/items/Pages/LoginPage';
import AccountPage from './components/items/Pages/AccountPage';
import MyNavBar from './components/items/Pages/MyNavbar';
/*function Router(props) {
return useRoutes(props.rootRoute);
}*/
function App() {
const routes = [
{ index: true, element: <LoginPage />, userGroup: "USER"},
{ path: 'levels', element: <AddLevel />, label: 'Обзор уровней', userGroup: "AUTH" },
{ path: 'nations', element: <AddNation />, label: 'Обзор наций', userGroup: "AUTH" },
{ path: 'tanks', element: <AddTank />, label: 'Обзор танков', userGroup: "AUTH"},
{ path: 'clients', element: <AddClient />, label: 'Обзор клиентов', userGroup: "ADMIN"},
{ path: 'checkPage', element: <PageForChecking />, label: 'Фильтр по танкам', userGroup: "AUTH"},
{ path: 'account', element: <AccountPage />, label: 'Аккаунт', userGroup: "AUTH"},
{ path: 'login', element: <LoginPage />, label: 'Вход', userGroup: "AUTH"}
];
return (
<div className='App'>
<BrowserRouter>
<MyNavBar links={routes}></MyNavBar>
<div className="content-div">
<Routes>
<Route element={<LoginPage />} path="/login" />
<Route element={<SingupPage />} path="/singup" />
<Route element={<PrivateRoutes userGroup="AUTH" />}>
<Route element={<AccountPage />} path="/account" />
<Route element={<AddTank />} path="/tanks" />
<Route element={<AddLevel />} path="/levels" />
<Route element={<PageForChecking />} path="/checkPage" />
<Route element={<AddNation />} path="/nations" exact />
<Route element={<AddNation />} path="*" />
</Route>
<Route element={<PrivateRoutes userGroup="ADMIN" />}>
<Route element={<AddClient />} path="/clients" />
</Route>
</Routes>
</div>
<footer className="border-top">
Footer
</footer>
</BrowserRouter>
</div>
);
}
export default App;

View File

@ -0,0 +1,14 @@
.add-client-input{
padding-left: 10px;
padding-right: 10px;
border: 3px solid;
border-radius: 10px;
border-color: #505050;
}
.add-level-button{
border-radius: 10px;
border-color: #505050;
background-color: #FFE430;
font-weight: 900;
}

View File

@ -0,0 +1,122 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import '../styles/App.css';
import ClientList from './items/GameClient/ClientList';
import './AddClient.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0";
export default function AddClient() {
const [clientItems, setClientItems] = useState([]);
//для создания нового уровня
const [clientNickName, setClientNickName] = useState();
const [clientEmail, setClientEmail] = useState();
const [clientBalance, setClientBalance] = useState();
const [pageNumbers, setPageNumbers] = useState([]);
const [pageNumber, setPageNumber] = useState();
useEffect(() => {
getUsers(1);
}, []);
const pageButtonOnClick = function (page) {
getUsers(page);
}
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
//загрузка всех имеющихся клиентов при старте
const getUsers = async function (page) {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader()
}
};
const requestUrl = host + `/users?page=${page}`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
setClientItems(data.first.content);
setPageNumber(data.first.number);
setPageNumbers(data.second);
}
//обновить список уровней
function CheckArray(){
getUsers(1);
}
//добавили условную отрисовку
return(
<div>
<div className="Group_create_level">
<h1>Генератор клиентов</h1>
<div>
<p style={{fontWeight: "900", marginTop:"10px"}}>
Введите никнейм клиента:
<input
className="add-client-input"
value={clientNickName}
onChange={e => setClientNickName(e.target.value)}
/>
</p>
<p style={{fontWeight: "900", marginTop:"10px"}}>
Введите почту клиента:
<input
className="add-client-input"
value={clientEmail}
onChange={e => setClientEmail(e.target.value)}
/>
</p>
<p style={{fontWeight: "900", marginTop:"10px"}}>
Введите баланс клиента:
<input
className="add-client-input"
value={clientBalance}
onChange={e => setClientBalance(e.target.value)}
/>
</p>
</div>
<div>
<button className='add-level-button'
onClick={CheckArray}
>
Вывести всех клиентов
</button>
</div>
</div>
<div className="Card_list">
{clientItems.length !== 0
?
<ClientList clientItems={clientItems}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо клиенты!</h1>
}
</div>
<div>
<p>
Pages:
</p>
<nav>
<ul className="pagination">
{pageNumbers.map((number) => (
<li className={`page-item ${number === pageNumber + 1 ? "active" : ""}`}
onClick={() => pageButtonOnClick(number)}>
<a className="page-link" href="#">{number}</a>
</li>
))}
</ul>
</nav>
</div>
</div>
);
}

View File

@ -0,0 +1,14 @@
.add-level-input{
padding-left: 10px;
padding-right: 10px;
border: 3px solid;
border-radius: 10px;
border-color: #505050;
}
.add-level-button{
border-radius: 10px;
border-color: #505050;
background-color: #FFE430;
font-weight: 900;
}

View File

@ -0,0 +1,108 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import '../styles/App.css';
import LevelList from './items/Level/LevelList';
import './AddLevel.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/level";
//компонент для просмотра, создания и удаления уровней
const AddLevel = () => {
const [levelItems, setLevelItems] = useState([]);
//для создания нового уровня
const [level, setLevel] = useState({level: ''});
//загрузка всех имеющихся уровней при старте
useEffect(() => {
getLevels();
}, [])
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
//загрузка всех имеющихся уровней при старте
const getLevels = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/getLevels`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setLevelItems(data);
}
//обновить список уровней
function CheckArray(){
getLevels();
}
//добавление нового уровня
const addNewLevel = async function (){
if(level.level === ''){
return;
}
else {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/?Level=${level.level}`;
const response = await fetch(requestUrl, requestParams);
getLevels();
setLevel({level: ''});
}
}
//добавили условную отрисовку
return(
<div>
<div className="Group_create_level">
<h1>Генератор уровней</h1>
<div>
<p style={{fontWeight: "900"}}>
Введите уровень:
<input
className="add-level-input"
value={level.level}
onChange={e => setLevel({level: e.target.value})}
/>
</p>
</div>
<div>
<button className='add-level-button'
onClick={addNewLevel}
>
Создать уровень
</button>
<button className='add-level-button'
onClick={CheckArray}
>
Вывести все уровни
</button>
</div>
</div>
<div className="Card_list">
{levelItems.length !== 0
?
<LevelList levelItems={levelItems}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо уровни!</h1>
}
</div>
</div>
);
};
export default AddLevel;

View File

@ -0,0 +1,14 @@
.add-nation-input{
padding-left: 10px;
padding-right: 10px;
border: 3px solid;
border-radius: 10px;
border-color: #505050;
}
.add-level-button{
border-radius: 10px;
border-color: #505050;
background-color: #FFE430;
font-weight: 900;
}

View File

@ -0,0 +1,107 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import '../styles/App.css';
import NationList from './items/Nation/NationList';
import './AddNation.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/nation";
//компонент для просмотра, создания и удаления уровней
const AddNation = () => {
const [nationItems, setNationItems] = useState([]);
//для создания нового уровня
const [nation, setNation] = useState({nation: ''});
//загрузка всех имеющихся уровней при старте
useEffect(() => {
getNations();
}, [])
//обновить список уровней
function CheckArray(){
getNations();
}
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const getNations = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/getNations`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setNationItems(data);
}
//добавление нового уровня
const addNewNation = async function (){
if(nation.nation === ''){
return;
}
else {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
console.log(JSON.stringify(nation));
const requestUrl = host + `/create/?nation=${nation.nation}`;
const response = await fetch(requestUrl, requestParams);
getNations();
setNation({nation: ''});
}
}
//добавили условную отрисовку
return(
<div>
<div className="Group_create_level">
<h1>Генератор наций</h1>
<div>
<p style={{fontWeight: "900"}}>
Введите нацию:
<input
className="add-nation-input"
value={nation.nation}
onChange={e => setNation({nation: e.target.value})}
/>
</p>
</div>
<div>
<button className='add-level-button'
onClick={addNewNation}
>
Создать нацию
</button>
<button className='add-level-button'
onClick={CheckArray}
>
Вывести все нации
</button>
</div>
</div>
<div className="Card_list">
{nationItems.length !== 0
?
<NationList nationItems={nationItems}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо нации!</h1>
}
</div>
</div>
);
};
export default AddNation;

View File

@ -0,0 +1,15 @@
.add-tank-input{
margin-top: 10px;
padding-left: 10px;
padding-right: 10px;
border: 3px solid;
border-radius: 10px;
border-color: #505050;
}
.add-level-button{
border-radius: 10px;
border-color: #505050;
background-color: #FFE430;
font-weight: 900;
}

View File

@ -0,0 +1,201 @@
import React, { useState, useEffect, useRef} from 'react';
import axios from 'axios';
import '../styles/App.css';
import TankList from './items/Tank/TankList';
import './AddTank.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/";
const AddTank = () => {
const [tankItems, setTankItems] = useState([]);
//для создания нового танка
const [tankName, setTankName] = useState({tankName: ''});
const [tankCost, setTankCost] = useState({tankCost: ''});
const [nationItems, setNationItems] = useState([]);
const [levelItems, setLevelItems] = useState([]);
//храним выбранные нацию и уровень для нового танка
const [chooiceNation, setChooiceNation] = useState();
const [chooiceLevel, setChooiceLevel] = useState();
const [fileByte, setFileByte] = useState(null);
const inputRef = useRef();
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
//загрузка всеГо необходимого при старте
useEffect(() => {
getTanks();
getLevels();
getNations();
}, [])
//загрузка всех имеющихся танков, а также уровней и наций при старте
const getTanks = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `tank/`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setTankItems(data);
}
const getNations = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `nation/getNations`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setNationItems(data);
}
//загрузка всех имеющихся уровней при старте
const getLevels = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `level/getLevels`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setLevelItems(data);
}
//обновить список танков
function CheckArray(){
getTanks();
getLevels();
getNations();
}
//добавление нового танка
async function addNewTank(){
console.log(tankName);
console.log(tankCost);
console.log(chooiceNation);
console.log(chooiceLevel);
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `tank/create/?firstName=${tankName.tankName}&nationId=${chooiceNation}&levelId=${chooiceLevel}&cost=${tankCost.tankCost}`;
const response = await fetch(requestUrl, requestParams);
CheckArray();
setTankCost({tankCost: ''});
setTankName({tankName: ''});
}
const getChoiceNation = (newId) => {
setChooiceNation(nationItems[newId - 1].id);
}
const getChoiceLevel = (newId) => {
setChooiceLevel(levelItems[newId - 1].id);
}
//добавили условную отрисовку
return(
<div>
<div className="Group_create_level">
<h1>Генератор танков</h1>
<div>
<p style={{fontWeight: "900"}}>
Выберите нацию:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите нацию</option>
{nationItems.map((nationItem) =>
<option
value={nationItem.nation}
key={nationItem.id}
>
{nationItem.nation}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите уровень:
<select style={{marginTop: "10px"}}
onChange={(event) => getChoiceLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Введите название:
<input
className="add-tank-input"
value={tankName.tankName}
onChange={e => setTankName({tankName: e.target.value})}
/>
</p>
<p style={{fontWeight: "900"}}>
Введите стоимость:
<input
className="add-tank-input"
value={tankCost.tankCost}
onChange={e => setTankCost({tankCost: e.target.value})}
/>
</p>
</div>
<div>
<button className='add-level-button'
onClick={addNewTank}
>
Создать танк
</button>
<button className='add-level-button'
onClick={CheckArray}
>
Вывести все танки
</button>
</div>
</div>
<div className="Card_list">
{tankItems.length !== 0
?
<TankList tankItems={tankItems}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо танки!</h1>
}
</div>
</div>
);
}
export default AddTank

View File

@ -0,0 +1,41 @@
import React from 'react';
import '../styles/App.css';
import { NavLink } from 'react-router-dom';
//компонент с кнопками навигации по сайту
const MainHead = (props) => {
return(
<div>
<div>
<h1 className="Main-label">Мир танков</h1>
</div>
<form className="collapse navbar-collapse">
<nav className="navbar navbar-expand-lg justify-content-around">
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" style={{backgroundColor: '#379dc2'}}>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="Main_head navbar-nav me-auto align-items-center">
{
props.links.map(route =>
<li key={route.path}
className="nav-item">
<div className="Button_Main_Group container p-2" div="div">
<NavLink className="nav-link btn border border-3 border-dark fs-4 lh-15" role="button"
to={route.path}>
{route.label}
</NavLink>
</div>
</li>
)
}
</ul>
</div>
</nav>
</form>
</div>
)
}
export default MainHead;

View File

@ -0,0 +1,133 @@
import React, { useState, useEffect, useRef} from 'react';
import axios from 'axios';
import '../styles/App.css';
import TankList from './items/Tank/TankList';
import './AddTank.css';
const PageForChecking = () => {
const[tankItems, setTankItems] = useState([]);
const [nationItems, setNationItems] = useState([]);
const [levelItems, setLevelItems] = useState([]);
const [chooiceNation, setChooiceNation] = useState();
const [chooiceFirstLevel, setChooiceFirstLevel] = useState();
const [chooiceSecondLevel, setChooiceSecondLevel] = useState();
//загрузка всех имеющихся танков, а также уровней и наций при старте
useEffect(() => {
axios.get('http://localhost:8080/level/')
.then((responce) => {
console.log(responce.data);
setLevelItems(responce.data)
});
axios.get('http://localhost:8080/nation/')
.then((responce) => {
console.log(responce.data);
setNationItems(responce.data)
});
}, [])
const getChoiceNation = (newId) => {
setChooiceNation(nationItems[newId - 1].nation);
}
const getChooiceFirstLevel = (newId) => {
setChooiceFirstLevel(levelItems[newId - 1].level);
}
const getChooiceSecondLevel = (newId) => {
setChooiceSecondLevel(levelItems[newId - 1].level);
}
function findList(){
console.log(chooiceNation);
console.log(chooiceFirstLevel);
console.log(chooiceSecondLevel);
axios.get('http://localhost:8080/tank/filteredList/?nation=' + chooiceNation + '&firstLevel=' + chooiceFirstLevel + '&secondLevel=' + chooiceSecondLevel)
.then((response) => {
setTankItems(response.data)
});
console.log(tankItems)
}
return(
<div>
<div className="Group_create_level">
<h1>Фильтрация танков</h1>
<div>
<p style={{fontWeight: "900"}}>
Выберите нацию:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите нацию</option>
{nationItems.map((nationItem) =>
<option
value={nationItem.nation}
key={nationItem.id}
>
{nationItem.nation}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите начальный диапазон уровней:
<select style={{marginTop: "10px"}}
onChange={(event) => getChooiceFirstLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите конечный диапазон уровней:
<select style={{marginTop: "10px"}}
onChange={(event) => getChooiceSecondLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
</div>
<button className='add-level-button'
onClick={findList}
>
Найти танки
</button>
</div>
<div className="Card_list">
{tankItems.length !== 0
?
<TankList tankItems={tankItems}
/>
:
<h1 style={{textAlign: 'center'}}>Отсутствуют какие-либо танки при таком фильтре!</h1>
}
</div>
</div>
);
}
export default PageForChecking

View File

@ -0,0 +1,9 @@
export default class UserSignupDto {
constructor(args) {
this.login = args.login;
this.email = args.email;
this.balance = args.balance;
this.password = args.password;
this.passwordConfirm = args.passwordConfirm;
}
}

View File

@ -0,0 +1,37 @@
.client-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.client-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.client-button-group{
display: flex;
width: 20%;
justify-content: space-around;
align-items: center;
}
.client-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,74 @@
import React, { useState } from 'react';
import axios from 'axios';
import './ClientItem.css';
import ModalClient from './ModalClient';
import ModalTankNation from '../Nation/ModalTankNation';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/";
const ClientItem = (data) => {
const [client, setClient] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
//состояние для вызова окна показа списка танков нации
const[modalNation, setModalNation] = useState(false);
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const deleteClient = async function (id) {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/user/${id}`;
await fetch(requestUrl, requestParams);
}
return (
<div className="client-card">
<p className="client-attribute"> id: {data.clientItem.id} </p>
<p className="client-attribute"> Никнейм: {data.clientItem.login} </p>
<p className="client-attribute"> Баланс: {data.clientItem.balance} </p>
<p className="client-attribute"> Почта: {data.clientItem.email} </p>
<p className="client-attribute"> Роль: {data.clientItem.role} </p>
<div className='client-button-group'>
<button className="client-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="client-button" type="button"
onClick={deleteClient(data.clientItem.id)}
>
Удалить
</button>
<button className="nation-button" type="button"
onClick={() => setModalNation(true)}
>
Список танков
</button>
<ModalClient
data={data.clientItem}
visible={modal}
setVisible={setModal}
/>
<ModalTankNation
data={data.clientItem}
visible={modalNation}
setVisible={setModalNation}
/>
</div>
</div>
);
};
export default ClientItem

View File

@ -0,0 +1,22 @@
import React, { useEffect} from 'react';
import ClientItem from './ClientItem';
const ClientList = (clients) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих клиентов:
</h1>
</div>
{clients.clientItems.map((clientItem) =>
<ClientItem
clientItem={clientItem}
key={clientItem.id}
/>
)}
</div>
);
}
export default ClientList;

View File

@ -0,0 +1,116 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import cl from '../GameClient/ModalClient.module.css';
import '../../AddClient.css';
const ModalClient = ({data, visible, setVisible}) => {
//для обновления уровня
const [clientNickName, setClientNickName] = useState(data.login);
const [clientEmail, setClientEmail] = useState(data.email);
const [clientBalance, setClientBalance] = useState(data.balance);
const [clientPassword, setClientPassword] = useState(data.password);
const [clientTank, setClientTank] = useState(null);
const [tankItems, setTankItems] = useState([]);
useEffect(() => {
/*console.log('Обращение к БД');
axios.get('http://localhost:8080/tank/')
.then((responce) => {
console.log(responce.data);
setTankItems(responce.data)
});*/
}, [])
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//добавление нового уровня
function updateLevel(){
/*axios.put('http://localhost:8080/client/' + data.id + '?login='
+ clientNickName + '&email=' + clientEmail + '&password=' + clientPassword + + '&balance=' + clientBalance + '&tankId=' + clientTank)
.then((response) => {
console.log("Обновление клиента с id " + data.id)
});
setVisible(false);*/
}
const getChoiceNation = (newId) => {
setClientTank(tankItems[newId - 1].id);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<p style={{marginTop: "10px"}}>
Никнейм:
<input
className="add-client-input"
value={clientNickName}
onChange={e => setClientNickName(e.target.value)}
/>
</p>
<p style={{marginTop: "10px"}}>
Почта:
<input
className="add-client-input"
value={clientEmail}
onChange={e => setClientEmail(e.target.value)}
/>
</p>
<p style={{marginTop: "10px"}}>
Баланс:
<input
className="add-client-input"
value={clientBalance}
onChange={e => setClientBalance(e.target.value)}
/>
</p>
<p style={{fontWeight: "900"}}>
Выберите новый танк:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите танк</option>
{tankItems.map((tankItem) =>
<option
value={tankItem.name}
key={tankItem.id}
>
{tankItem.name}
</option>
)}
</select>
</p>
<p style={{marginTop: "10px"}}>
Пароль:
<input
className="add-client-input"
value={clientPassword}
onChange={e => setClientPassword(e.target.value)}
/>
</p>
<button
style={{marginTop: "10px"}}
className={cl.modalButton}
type="button"
onClick={updateLevel}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalClient

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: inline-block;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,37 @@
.level-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.level-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.level-button-group{
display: flex;
width: 20%;
justify-content: space-around;
align-items: center;
}
.level-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,58 @@
import React, { useState } from 'react';
import axios from 'axios';
import './LevelItem.css';
import ModalLevel from './ModalLevel';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/level";
//отвечает за отдельно взятый уровень (вывод карточки с ним)
const LevelItem = (data) => {
const [level, setLevel] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const deleteLevel = async function (id) {
const requestParams = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/${id}`;
await fetch(requestUrl, requestParams);
}
return (
<div className="level-card">
<p className="level-attribute"> id: {data.levelItem.id} </p>
<p className="level-attribute"> уровень: {data.levelItem.level} </p>
<div className='level-button-group'>
<button className="level-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="level-button" type="button"
onClick={event => deleteLevel(data.levelItem.id)}
>
Удалить
</button>
<ModalLevel
data={data.levelItem}
visible={modal}
setVisible={setModal}
/>
</div>
</div>
);
};
export default LevelItem;

View File

@ -0,0 +1,25 @@
import React, { useEffect} from 'react';
import LevelItem from './LevelItem';
//const host = import.meta.env.VITE_API_URL;
//отвечает за список всех уровней. Передаём сюда пропсом массив уровней
const LevelList = (levels) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих уровней:
</h1>
</div>
{levels.levelItems.map((levelItem) =>
<LevelItem
levelItem={levelItem}
key={levelItem.id}
/>
)}
</div>
);
};
export default LevelList;

View File

@ -0,0 +1,62 @@
import React, { useState } from 'react';
import axios from 'axios';
import cl from './ModalLevel.module.css';
import '../../AddLevel.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/level";
const ModalLevel = ({data, visible, setVisible}) => {
//для обновления уровня
const [level, setLevel] = useState(data.level);
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
//добавление нового уровня
const updateLevel = async function () {
const requestParams = {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
console.log(level);
const requestUrl = host + `/${data.id}?Level=${level}`;
const response = await fetch(requestUrl, requestParams);
console.log("Обновление успешно!")
setVisible(false);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<input
className="add-level-input"
value={level}
onChange={e => setLevel(e.target.value)}
/>
<button
className={cl.modalButton}
type="button"
onClick={event => updateLevel()}
>
Сохранить
</button>
</div>
</div>
);
};
export default ModalLevel;

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: flex;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,61 @@
import React, { useState } from 'react';
import axios from 'axios';
import cl from './ModalNation.module.css';
import '../../AddNation.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/nation";
const ModalNation = ({data, visible, setVisible}) => {
//для обновления уровня
const [nation, setNation] = useState(data.nation);
const nullId = 0;
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const updateNation = async function () {
const requestParams = {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/${data.id}?nation=${nation}&tankId=${nullId}`;
const response = await fetch(requestUrl, requestParams);
console.log("Обновление успешно!")
setVisible(false);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<input
className="add-nation-input"
value={nation}
onChange={e => setNation(e.target.value)}
/>
<button
className={cl.modalButton}
type="button"
onClick={event => updateNation()}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalNation

View File

@ -0,0 +1,34 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: flex;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
justify-content: space-between;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,42 @@
import React, { useState, useEffect } from 'react';
import cl from './ModalNation.module.css';
import '../../AddNation.css';
import TankList from '../Tank/TankList';
import { useFetcher } from 'react-router-dom';
const ModalTankNation = ({data, visible, setVisible}) => {
//для обновления уровня
const [nation, setNation] = useState(data.tanks);
//для контроля видимости модалки
const rootClasses = [cl.myModal];
//загрузка всех имеющихся танков, а также уровней и наций при старте
useEffect(() => {
console.log("Загрузка МОДАЛКИ")
console.log(nation);
}, [])
if(visible)
{
rootClasses.push(cl.active);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
{nation.length !== 0
?
<TankList
label={"Список танков у нации"}
tankItems={nation}
/>
:
<h1 style={{textAlign: 'center'}}>В БД отсутствуют какие-либо танки данной нации!</h1>
}
</div>
</div>
);
}
export default ModalTankNation

View File

@ -0,0 +1,38 @@
.nation-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.nation-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.nation-button-group{
display: flex;
margin: 10px;
width: 20%;
justify-content: space-around;
align-items: center;
}
.nation-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,75 @@
import React, { useState } from 'react';
import axios from 'axios';
import './NationItem.css';
import ModalNation from './ModalNation';
import ModalTankNation from './ModalTankNation';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/nation";
//отвечает за отдельно взятую нацию (вывод карточки с ним)
const NationItem = (data) => {
const [nation, setNation] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
//состояние для вызова окна показа списка танков нации
const[modalNation, setModalNation] = useState(false);
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const deleteFecth = async function (id) {
const requestParams = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/${id}`;
await fetch(requestUrl, requestParams);
}
/*function deleteNation(id){
deleteFecth(id)
}*/
return (
<div className="nation-card">
<p className="nation-attribute"> id: {data.nationItem.id} </p>
<p className="nation-attribute"> нация: {data.nationItem.nation} </p>
<div className='nation-button-group'>
<button className="nation-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="nation-button" type="button"
onClick={event => deleteFecth(data.nationItem.id)}
>
Удалить
</button>
<button className="nation-button" type="button"
onClick={() => setModalNation(true)}
>
Список танков
</button>
<ModalNation
data={data.nationItem}
visible={modal}
setVisible={setModal}
/>
<ModalTankNation
data={data.nationItem}
visible={modalNation}
setVisible={setModalNation}
/>
</div>
</div>
);
}
export default NationItem

View File

@ -0,0 +1,22 @@
import React from 'react'
import NationItem from './NationItem';
const NationList = (nations) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих наций:
</h1>
</div>
{nations.nationItems.map((nationItem) =>
<NationItem
nationItem={nationItem}
key={nationItem.id}
/>
)}
</div>
);
}
export default NationList

View File

@ -0,0 +1,148 @@
import { useEffect, useState, useRef } from "react";
import { useNavigate } from 'react-router-dom';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0";
const AccountPage = function () {
const [currentUser, setCurrentUser] = useState({});
const loginInput = useRef();
const emailInput = useRef();
const navigate = useNavigate();
useEffect(() => {
getUser().then(user => setCurrentUser(user));
}, []);
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const login = async function () {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(currentUser),
};
const requestUrl = hostURL + "/jwt/login";
const response = await fetch(requestUrl, requestParams);
const result = await response.text();
if (response.status === 200) {
localStorage.setItem("token", result);
localStorage.setItem("user", currentUser.login);
getRole(result);
} else {
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("role");
}
}
const getRole = async function (token) {
const requestParams = {
method: "GET",
headers: {
"Content-Type": "application/json"
}
};
const requestUrl = hostURL + `/who_am_i?token=${token}`;
const response = await fetch(requestUrl, requestParams);
const result = await response.text();
localStorage.setItem("role", result);
window.dispatchEvent(new Event("storage"));
}
const updateUser = async function () {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
},
body: JSON.stringify(currentUser),
};
const requestUrl = host + `/user`;
const response = await fetch(requestUrl, requestParams);
const result = await response.text();
return result;
}
const getUser = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
let login = localStorage.getItem("user");
const requestUrl = host + `/user?login=${login}`;
const response = await fetch(requestUrl, requestParams);
const user = await response.json();
return user;
}
const onSubmit = function (event) {
event.preventDefault();
updateUser().then((result) => {
alert(result);
if (result === "Profile updated") {
login();
}
});
}
const onInput = function (event, fieldName) {
setCurrentUser(oldUser => ({
...oldUser, [fieldName]: event.target.value
}));
}
const logoutButtonOnClick = function () {
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("role");
window.dispatchEvent(new Event("storage"));
navigate("/login");
}
return (
<>
<div class="border-bottom pb-3 mb-3">
<button class="btn btn-primary"
onClick={logoutButtonOnClick}>
Log Out
</button>
</div>
<h4 className="mb-4">Update profile</h4>
<form onSubmit={onSubmit}>
<div className="mb-3">
<p className="mb-1">New Login</p>
<input className="form-control" type="text" required
ref={loginInput} value={currentUser.login}
onInput={(event) => onInput(event, "login")} />
</div>
<div className="mb-3">
<p className="mb-1">New Email</p>
<input className="form-control" type="text" required
ref={emailInput} value={currentUser.email}
onInput={(event) => onInput(event, "email")} />
</div>
<div className="mb-3">
<p className="mb-1">Enter password</p>
<input className="form-control" type="password" required
ref={emailInput}
onInput={(event) => onInput(event, "password")} />
</div>
<button type="submit" className="btn btn-primary">
Save
</button>
</form>
</>
)
}
export default AccountPage;

View File

@ -0,0 +1,90 @@
import { useState, useEffect } from "react";
import { Link, useNavigate } from 'react-router-dom';
import { useRef } from "react";
const hostURL = "http://localhost:8080";
const LoginPage = function () {
const loginInput = useRef();
const passwordInput = useRef();
const navigate = useNavigate();
useEffect(() => {
}, []);
const login = async function (login, password) {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({login: login, password: password}),
};
const response = await fetch(hostURL + "/jwt/login", requestParams);
const result = await response.text();
if (response.status === 200) {
localStorage.setItem("token", result);
localStorage.setItem("user", login);
getRole(result);
} else {
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("role");
}
}
const getRole = async function (token) {
const requestParams = {
method: "GET",
headers: {
"Content-Type": "application/json"
}
};
const requestUrl = hostURL + `/who_am_i?token=${token}`;
const response = await fetch(requestUrl, requestParams);
const result = await response.text();
localStorage.setItem("role", result);
window.dispatchEvent(new Event("storage"));
navigate("/index");
}
const loginFormOnSubmit = function (event) {
event.preventDefault();
login(loginInput.current.value, passwordInput.current.value);
};
return (
<>
<div>
<form onSubmit={(event) => loginFormOnSubmit(event)}>
<div className="mb-3">
<p className="mb-1">Login</p>
<input className="form-control"
type="text" required autofocus
ref={loginInput} />
</div>
<div className="mb-3">
<p className="mb-1">Password</p>
<input className="form-control"
type="password" required
ref={passwordInput} />
</div>
<div className="mb-3">
<button type="submit" className="btn btn-success">
Sing in
</button>
</div>
<div>
<p>
<span>Not a member yet?&nbsp;</span>
<Link to="/singup">Sing Up here</Link>
</p>
</div>
</form>
</div>
</>
)
}
export default LoginPage;

View File

@ -0,0 +1,63 @@
import { useState, useEffect } from "react";
import { NavLink } from 'react-router-dom';
import '../../../styles/App.css';
const MyNavBar = function (props) {
const [userRole, setUserRole] = useState("NONE");
useEffect(() => {
window.addEventListener("storage", () => {
getUserRole();
});
getUserRole();
}, [])
const getUserRole = function () {
const role = localStorage.getItem("role") || "NONE";
setUserRole(role);
}
const validate = function (userGroup) {
if ((userGroup === "AUTH" && userRole !== "NONE") ||
(userGroup === userRole)) {
return true;
}
return false;
}
return (
<div>
<div>
<h1 className="Main-label">Мир танков</h1>
</div>
<form className="collapse navbar-collapse">
<nav className="navbar navbar-expand-lg justify-content-around">
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="Main_head navbar-nav me-auto align-items-center">
{
props.links.map(route => {
if (validate(route.userGroup)) {
return (
<li key={route.path}
className="nav-item">
<div className="Button_Main_Group container p-2" div="div">
<NavLink className="nav-link btn border border-3 border-dark fs-4 lh-15" role="button"
to={route.path}>
{route.label}
</NavLink>
</div>
</li>
)
}
})
}
</ul>
</div>
</nav>
</form>
</div>
);
}
export default MyNavBar;

View File

@ -0,0 +1,47 @@
import { Outlet, Navigate, useNavigate } from 'react-router-dom';
import { useEffect, useState } from "react";
const PrivateRoutes = (props) => {
const navigate = useNavigate();
useEffect(() => {
window.addEventListener("storage", () => {
let token = localStorage.getItem("token");
if (token) {
getRole(token).then((role) => {
if (localStorage.getItem("role") !== role) {
localStorage.removeItem("token");
localStorage.removeItem("user");
localStorage.removeItem("role");
window.dispatchEvent(new Event("storage"));
navigate("/index");
}
});
}
});
}, [])
const getRole = async function (token) {
const requestParams = {
method: "GET",
headers: {
"Content-Type": "application/json"
}
};
const requestUrl = `http://localhost:8080/who_am_i?token=${token}`;
const response = await fetch(requestUrl, requestParams);
const result = await response.text();
return result;
}
let isAllowed = false;
let userRole = localStorage.getItem("role");
if ((props.userGroup === "AUTH" && userRole) || (props.userGroup === userRole)) {
isAllowed = true;
}
return isAllowed ? <Outlet /> : <Navigate to="/login" />;
}
export default PrivateRoutes;

View File

@ -0,0 +1,94 @@
import { useState, useEffect } from "react";
import { Link } from 'react-router-dom';
import { useRef } from "react";
const hostURL = "http://localhost:8080";
const SingupPage = function () {
const loginInput = useRef();
const emailInput = useRef();
const passwordInput = useRef();
const passwordConfirmInput = useRef();
const balanceInput = useRef();
useEffect(() => {
}, []);
const singup = async function (userSinginDto) {
const requestParams = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userSinginDto),
};
const response = await fetch(hostURL + "/sing_up", requestParams);
const result = await response.text();
alert(result);
}
const singupFormOnSubmit = function (event) {
event.preventDefault();
const userSinginDto = {
login: loginInput.current.value,
email: emailInput.current.value,
balance: balanceInput.current.value,
password: passwordInput.current.value,
passwordConfirm: passwordConfirmInput.current.value
}
singup(userSinginDto);
};
return (
<>
<div>
<form onSubmit={(event) => singupFormOnSubmit(event)}>
<div className="mb-3">
<p className="mb-1">Login</p>
<input className="form-control"
type="text" required maxlength="64"
ref={loginInput} />
</div>
<div className="mb-3">
<p className="mb-1">Email</p>
<input className="form-control"
type="text" required
ref={emailInput} />
</div>
<div className="mb-3">
<p className="mb-1">Balance</p>
<input className="form-control"
type="text" required minlength="3" maxlength="64"
ref={balanceInput} />
</div>
<div className="mb-3">
<p className="mb-1">Password</p>
<input className="form-control"
type="password" required minlength="3" maxlength="64"
ref={passwordInput} />
</div>
<div className="mb-3">
<p className="mb-1">Confirm Password</p>
<input className="form-control"
type="password" required minlength="3" maxlength="64"
ref={passwordConfirmInput} />
</div>
<div className="mb-3">
<button type="submit" className="btn btn-success">
Create account
</button>
</div>
<div>
<p>
<span>Already have an account?&nbsp;</span>
<Link to="/login">Sing In here</Link>
</p>
</div>
</form>
</div>
</>
)
}
export default SingupPage;

View File

@ -0,0 +1,158 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import cl from './ModalTank.module.css';
import '../../AddTank.css';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/";
const ModalTank = ({data, visible, setVisible}) => {
//для обновления танка
const [tankName, setTankName] = useState(data.name);
const [tankCost, setTankCost] = useState(data.cost);
const [chooiceNation, setChoiceNation] = useState(data.nation.id);
const [chooiceLevel, setChoiceLevel] = useState(data.nation.id);
const [nationItems, setNationItems] = useState([]);
const [levelItems, setLevelItems] = useState([]);
//для контроля видимости модалки
const rootClasses = [cl.myModal];
if(visible)
{
rootClasses.push(cl.active);
}
//загрузка всего необходимого при старте
useEffect(() => {
getLevels();
getNations();
}, [])
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const getNations = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `nation/getNations`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setNationItems(data);
}
//загрузка всех имеющихся уровней при старте
const getLevels = async function () {
const requestParams = {
method: "GET",
headers: {
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `level/getLevels`;
const response = await fetch(requestUrl, requestParams);
const data = await response.json();
console.log(data);
setLevelItems(data);
}
const updateTank = async function () {
const requestParams = {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
console.log(data);
console.log(chooiceNation);
const requestUrl = host + `tank/${data.id}?firstName=${tankName}&nationId=${chooiceNation}&levelId=${chooiceLevel}&cost=${tankCost}`;
const response = await fetch(requestUrl, requestParams);
console.log("Обновление успешно!")
setVisible(false);
}
const getChoiceNation = (newId) => {
setChoiceNation(nationItems[newId - 1].id);
}
const getChoiceLevel = (newId) => {
setChoiceLevel(levelItems[newId - 1].id);
}
return (
<div className={rootClasses.join(' ')} onClick={() => setVisible(false)}>
<div className={cl.myModalContent} onClick={(e) => e.stopPropagation()}>
<p>
Название:
<input
className="add-tank-input"
value={tankName}
onChange={e => setTankName(e.target.value)}
/>
</p>
<p>
Стоимость:
<input
className="add-tank-input"
value={tankCost}
onChange={e => setTankCost(e.target.value)}
/>
</p>
<p style={{fontWeight: "900"}}>
Выберите нацию:
<select
onChange={(event) => getChoiceNation(event.target.selectedIndex)}
>
<option selected>Выберите нацию</option>
{nationItems.map((nationItem) =>
<option
value={nationItem.nation}
key={nationItem.id}
>
{nationItem.nation}
</option>
)}
</select>
</p>
<p style={{fontWeight: "900"}}>
Выберите уровень:
<select style={{marginTop: "10px"}}
onChange={(event) => getChoiceLevel(event.target.selectedIndex)}
>
<option selected>Выберите уровень</option>
{levelItems.map((levelItem) =>
<option
value={levelItem.level}
key={levelItem.id}
>
{levelItem.level}
</option>
)}
</select>
</p>
<button
className={cl.modalButton}
type="button"
onClick={event => updateTank()}
>
Сохранить
</button>
</div>
</div>
);
}
export default ModalTank

View File

@ -0,0 +1,33 @@
.myModal{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background: rgba(0, 0, 0, 0.8);
}
.myModal.active{
display: flex;
justify-content: center;
align-items: center;
}
.myModalContent{
display: inline-block;
padding: 15px;
background: #FF652F;
border-radius: 16px;
min-width: 300px;
min-height: 100px;
align-items: center;
}
.modalButton{
padding: 5px;
border-radius: 10px;
background-color: #FFE430;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,37 @@
.tank-card{
display: flex;
width: 100%;
padding: 15px;
margin-top: 5px;
border: 5px solid;
border-color: #14A76C;
border-radius: 10px;
justify-content: space-around;
align-items: center;
font-family: Courier, monospace;
font-weight: 900;
}
.tank-attribute{
padding: 5px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
align-items: center;
}
.tank-button-group{
display: flex;
width: 20%;
justify-content: space-around;
align-items: center;
}
.tank-button{
padding: 10px;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: 900;
}

View File

@ -0,0 +1,60 @@
import React, { useState } from 'react';
import axios from 'axios';
import './TankItem.css';
import ModalTank from './ModalTank';
const hostURL = "http://localhost:8080";
const host = hostURL + "/api/1.0/tank";
const TankItem = (data) => {
const [tank, setTank] = useState(null);
//состояние для контроля вызова модального окна
const[modal, setModal] = useState(false);
const getTokenForHeader = function () {
return "Bearer " + localStorage.getItem("token");
}
const deleteTank = async function (id) {
const requestParams = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": getTokenForHeader(),
}
};
const requestUrl = host + `/${id}`;
await fetch(requestUrl, requestParams);
}
return (
<div className="tank-card">
<p className="tank-attribute"> id: {data.tankItem.id} </p>
<p className="tank-attribute"> название: {data.tankItem.name} </p>
<p className="tank-attribute"> уровень: {data.tankItem.level.level} </p>
<p className="tank-attribute"> нация: {data.tankItem.nation.nation} </p>
<p className="tank-attribute"> стоимость: {data.tankItem.cost} </p>
<div className='tank-button-group'>
<button className="tank-button" type="button"
onClick={() => setModal(true)}
>
Редактировать
</button>
<button className="tank-button" type="button"
onClick={event => deleteTank(data.tankItem.id)}
>
Удалить
</button>
<ModalTank
data={data.tankItem}
visible={modal}
setVisible={setModal}
/>
</div>
</div>
);
}
export default TankItem

View File

@ -0,0 +1,22 @@
import React from 'react'
import TankItem from './TankItem';
const TankList = (tanks) => {
return (
<div>
<div>
<h1 style={{textAlign: 'center', fontFamily: 'courier, monospace', background: '#FF652F', borderRadius: '10px'}}>
Список существующих наций:
</h1>
</div>
{tanks.tankItems.map((tankItem) =>
<TankItem
tankItem={tankItem}
key={tankItem.id}
/>
)}
</div>
);
}
export default TankList;

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

View File

@ -0,0 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
)

View File

@ -0,0 +1,102 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/*#505050*/
#root{
display: flex;
justify-content: center;
background-color: #151719;
background-image: url("../images/wot-is-4.jpg");
background-size: cover;
background-attachment: fixed;
font-family: Courier, monospace;
font-weight: 900;
}
.body{
margin: 0; /* Убираем отступы */
height: 100%; /* Высота страницы */
}
.App{
width: 1300px;
}
.Group_create_level{
display: flex;
padding: 15px;
background-color: #FFE430;
opacity: 0.8;
border-radius: 10px;
margin-top: 15px;
justify-content: space-around;
align-items: center;
}
.Card_list{
padding: 15px;
border: 5px solid;
border-color: #14A76C;
background-color: #151719;
opacity: 0.9;
border-radius: 10px;
margin-top: 15px;
align-items: center;
}
.Main_head{
display: flex;
padding: 15px;
margin-top: 30px;
border: 5px solid;
border-color: #FF652F;
border-radius: 10px;
background-color: #151719;
opacity: 0.9;
justify-content: space-between;
align-items: center;
}
.Button_Main_Group{
padding: 5px;
border-color: #FF652F;
border-radius: 10px;
background-color: #FF652F;
font-family: Courier, monospace;
font-weight: bold;
align-items: center;
}
.add-level-button{
padding: 10px;
margin-right: 15px;
}
.Main-label{
display: flex;
padding-top: 16px;
font-size: 7vw;
font-variant: small-caps;
font-stretch: ultra-expanded;
justify-content: space-around;
align-items: center;
color: #505050;
-webkit-text-stroke-width: 3.0px;
-webkit-text-stroke-color: #000000;
}
li {
list-style-type: none;
/* Убираем маркеры */
}
ul {
margin-left: 0;
/* Отступ слева в браузере IE и Opera */
padding-left: 0;
/* Отступ слева в браузере Firefox, Safari, Chrome */
}

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1,10 +0,0 @@
import React from 'react';
function App() {
return (
<div className="App">
</div>
);
}
export default App;

View File

@ -1,11 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
//document.getElementById('root'); - для работы этого надо поставит запятую после <App/>
//и делаем import 'react-dom', вместо 'react-dom/client'
);

View File

@ -35,3 +35,5 @@ out/
### VS Code ###
.vscode/
*.db

View File

@ -1,10 +1,10 @@
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.8'
id 'org.springframework.boot' version '2.6.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
}
group = 'com.example'
group = 'ru.ulstu.is'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
@ -12,9 +12,30 @@ repositories {
mavenCentral()
}
jar {
enabled = false
}
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
implementation 'org.webjars:bootstrap:5.1.3'
implementation 'org.webjars:jquery:3.6.0'
implementation 'org.webjars:font-awesome:6.1.0'
implementation 'com.auth0:java-jwt:4.4.0'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -205,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1 +1 @@
rootProject.name = 'spring_online_calculator'
rootProject.name = 'premium_store'

View File

@ -1,13 +0,0 @@
package com.example.spring_online_calculator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringOnlineCalculatorApplication {
public static void main(String[] args) {
SpringApplication.run(SpringOnlineCalculatorApplication.class, args);
}
}

View File

@ -0,0 +1,11 @@
package premium_store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PremiumStoreApplication {
public static void main(String[] args) {
SpringApplication.run(PremiumStoreApplication.class, args);
}
}

View File

@ -0,0 +1,28 @@
package premium_store.configuration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import premium_store.configuration.jwt.JwtFilter;
@Configuration
public class OpenAPI30Configuration {
public static final String API_PREFIX = "/api/1.0";
@Bean
public OpenAPI customizeOpenAPI() {
final String securitySchemeName = JwtFilter.TOKEN_BEGIN_STR;
return new OpenAPI()
.addSecurityItem(new SecurityRequirement()
.addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}

View File

@ -0,0 +1,14 @@
package premium_store.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfiguration {
@Bean
public PasswordEncoder createPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,91 @@
package premium_store.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import premium_store.configuration.jwt.JwtFilter;
import premium_store.controller.controller.GameClientController;
import premium_store.model.UserRole;
import premium_store.service.GameClientService;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
securedEnabled = true
)
public class SecurityConfiguration {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
public static final String SPA_URL_MASK = "/{path:[^\\.]*}";
private final GameClientService userService;
private final JwtFilter jwtFilter;
public SecurityConfiguration(GameClientService userService) {
this.userService = userService;
this.jwtFilter = new JwtFilter(userService);
createAdminOnStartup();
}
private void createAdminOnStartup() {
final String admin = "admin";
if (userService.findByLogin(admin) == null) {
log.info("Admin user successfully created");
userService.addClient(admin, "admin@gmail.com", admin, 0, admin, UserRole.ADMIN);
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/", SPA_URL_MASK).permitAll()
.antMatchers(HttpMethod.POST, GameClientController.URL_LOGIN).permitAll()
.antMatchers(HttpMethod.POST, GameClientController.URL_SING_UP).permitAll()
.antMatchers(HttpMethod.POST, GameClientController.URL_WHO_AM_I).permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.anonymous();
return http.build();
}
@Bean
public AuthenticationManager authenticationManagerBean(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http
.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userService);
return authenticationManagerBuilder.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/*.js")
.antMatchers("/*.html")
.antMatchers("/*.css")
.antMatchers("/assets/**")
.antMatchers("/favicon.ico")
.antMatchers("/.js", "/.css")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/webjars/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/v3/api-docs/**")
.antMatchers("/h2-console");
}
}

View File

@ -0,0 +1,30 @@
package premium_store.configuration;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController(SecurityConfiguration.SPA_URL_MASK).setViewName("forward:/");
registry.addViewController("/notFound").setViewName("forward:/");
}
@Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notFound"));
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}

View File

@ -0,0 +1,11 @@
package premium_store.configuration.jwt;
public class JwtException extends RuntimeException {
public JwtException(Throwable throwable) {
super(throwable);
}
public JwtException(String message) {
super(message);
}
}

View File

@ -0,0 +1,73 @@
package premium_store.configuration.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import premium_store.service.GameClientService;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtFilter extends GenericFilterBean {
private static final String AUTHORIZATION = "Authorization";
public static final String TOKEN_BEGIN_STR = "Bearer ";
private final GameClientService userService;
public JwtFilter(GameClientService userService) {
this.userService = userService;
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearer = request.getHeader(AUTHORIZATION);
if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) {
return bearer.substring(TOKEN_BEGIN_STR.length());
}
return null;
}
private void raiseException(ServletResponse response, int status, String message) throws IOException {
if (response instanceof final HttpServletResponse httpResponse) {
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpResponse.setStatus(status);
final byte[] body = new ObjectMapper().writeValueAsBytes(message);
response.getOutputStream().write(body);
}
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof final HttpServletRequest httpRequest) {
final String token = getTokenFromRequest(httpRequest);
if (StringUtils.hasText(token)) {
try {
final UserDetails user = userService.loadUserByToken(token);
final UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (JwtException e) {
raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
} catch (Exception e) {
e.printStackTrace();
raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
String.format("Internal error: %s", e.getMessage()));
return;
}
}
}
chain.doFilter(request, response);
}
}

View File

@ -0,0 +1,27 @@
package premium_store.configuration.jwt;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true)
public class JwtProperties {
private String devToken = "";
private Boolean isDev = true;
public String getDevToken() {
return devToken;
}
public void setDevToken(String devToken) {
this.devToken = devToken;
}
public Boolean isDev() {
return isDev;
}
public void setDev(Boolean dev) {
isDev = dev;
}
}

View File

@ -0,0 +1,107 @@
package premium_store.configuration.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;
@Component
public class JwtProvider {
private final static Logger LOG = LoggerFactory.getLogger(JwtProvider.class);
private final static byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private final static String ISSUER = "auth0";
private final Algorithm algorithm;
private final JWTVerifier verifier;
public JwtProvider(JwtProperties jwtProperties) {
if (!jwtProperties.isDev()) {
LOG.info("Generate new JWT key for prod");
try {
final MessageDigest salt = MessageDigest.getInstance("SHA-256");
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
LOG.info("Use generated JWT key for prod \n{}", bytesToHex(salt.digest()));
algorithm = Algorithm.HMAC256(bytesToHex(salt.digest()));
} catch (NoSuchAlgorithmException e) {
throw new JwtException(e);
}
} else {
LOG.info("Use default JWT key for dev \n{}", jwtProperties.getDevToken());
algorithm = Algorithm.HMAC256(jwtProperties.getDevToken());
}
verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
}
private static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
public String generateToken(String login) {
final Date issueDate = Date.from(LocalDate.now()
.atStartOfDay(ZoneId.systemDefault())
.toInstant());
final Date expireDate = Date.from(LocalDate.now()
.plusDays(15)
.atStartOfDay(ZoneId.systemDefault())
.toInstant());
return JWT.create()
.withIssuer(ISSUER)
.withIssuedAt(issueDate)
.withExpiresAt(expireDate)
.withSubject(login)
.sign(algorithm);
}
private DecodedJWT validateToken(String token) {
try {
return verifier.verify(token);
} catch (JWTVerificationException e) {
throw new JwtException(String.format("Token verification error: %s", e.getMessage()));
}
}
public boolean isTokenValid(String token) {
if (!StringUtils.hasText(token)) {
return false;
}
try {
validateToken(token);
return true;
} catch (JwtException e) {
LOG.error(e.getMessage());
return false;
}
}
public Optional<String> getLoginFromToken(String token) {
try {
return Optional.ofNullable(validateToken(token).getSubject());
} catch (JwtException e) {
LOG.error(e.getMessage());
return Optional.empty();
}
}
}

View File

@ -0,0 +1,102 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.GameClient;
import premium_store.model.UserRole;
import java.util.ArrayList;
import java.util.List;
//класс, который соединяет танки клиента в одну строчку (нам так захотелось)
public class ClientDTO {
private long id;
private String login;
private String password;
private String email;
private Integer balance;
private List<TankDTO> tanks = new ArrayList<>();
private UserRole role;
public ClientDTO(){ }
public ClientDTO(GameClient gameClient){
this.id = gameClient.getId();
this.login = gameClient.getLogin();
this.password = gameClient.getPassword();
this.email = gameClient.getEmail();
this.balance = gameClient.getBalance();
if(gameClient.getTanks() != null && gameClient.getTanks().size() > 0){
this.tanks = gameClient.getTanks().stream()
.map(TankDTO::new)
.toList();
}
this.role = gameClient.getRole();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId(){
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLogin(){
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail(){
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getBalance(){
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
public List<TankDTO> getTanks(){
return tanks;
}
public Long getIdLastTanks(){
if(tanks.size() > 0){
return tanks.get(tanks.size() - 1).getId();
} else {
return null;
}
}
public void setTanks(List<TankDTO> tanks) {
this.tanks = tanks;
}
public UserRole getRole() {
return role;
}
public void setRole(UserRole role) {
this.role = role;
}
}

View File

@ -0,0 +1,47 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.Nation;
import java.util.List;
public class FullNationDTO {
public Long id;
public String nation;
public List<TankDTO> tanks;
public FullNationDTO(){ }
public FullNationDTO(Nation nation){
this.id = nation.getId();
this.nation = nation.getNation();
this.tanks = nation.getTanksOfThisNation().stream()
.map(TankDTO::new)
.toList();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNation() {
return nation;
}
public void setNation(String nation) {
this.nation = nation;
}
public List<TankDTO> getTanksOfThisNation() {
return tanks;
}
public void setTanks(List<TankDTO> tanks) {
this.tanks = tanks;
}
}

View File

@ -0,0 +1,33 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.TankLevel;
public class LevelDTO {
private Long id;
private int level;
public LevelDTO(){}
public LevelDTO(TankLevel level){
this.id = level.getId();
this.level = level.getLevel();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}
public int getLevel(){
return level;
}
public void setLevel(int level) {
this.level = level;
}
}

View File

@ -0,0 +1,33 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.Nation;
public class SimpleNationDTO {
private Long id;
private String nation;
public SimpleNationDTO(){}
public SimpleNationDTO(Nation nation){
this.id = nation.getId();
this.nation = nation.getNation();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}
public String getNation(){
return nation;
}
public void setNation(String nation){
this.nation = nation;
}
}

View File

@ -0,0 +1,39 @@
package premium_store.controller.DTO;
public class SortDTO {
private String nation;
private int firstLevel;
private int secondLevel;
public SortDTO(){}
public SortDTO(String nation, int firstLevel, int secondLevel){
this.nation = nation;
this.firstLevel = firstLevel;
this.secondLevel = secondLevel;
}
public String getNation(){
return nation;
}
public int getFirstLevel() {
return firstLevel;
}
public int getSecondLevel() {
return secondLevel;
}
public void setNation(String nation){
this.nation = nation;
}
public void setFirstLevel(int firstLevel){
this.firstLevel = firstLevel;
}
public void setSecondLevel(int secondLevel) {
this.secondLevel = secondLevel;
}
}

View File

@ -0,0 +1,90 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.GameClient;
import premium_store.model.UserRole;
//класс, который соединяет танки клиента в одну строчку (нам так захотелось)
public class SupportClientDTO {
private long id;
private String login;
private String password;
private String email;
private Integer balance;
private Long tankId;
private UserRole role;
public SupportClientDTO(){ }
public SupportClientDTO(GameClient gameClient){
this.id = gameClient.getId();
this.login = gameClient.getLogin();
this.password = gameClient.getPassword();
this.email = gameClient.getEmail();
this.balance = gameClient.getBalance();
this.role = gameClient.getRole();
if(gameClient.getTanks().size() >= 1){
this.tankId = gameClient.getTanks().get(gameClient.getTanks().size() - 1).getId();
}
else {
this.tankId = null;
}
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId(){
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLogin(){
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword(){
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail(){
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getBalance(){
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
public Long getTankId(){
return tankId;
}
public void setTankId(Long tankId) {
this.tankId = tankId;
}
public UserRole getRole() {
return role;
}
public void setRole(UserRole role) {
this.role = role;
}
}

View File

@ -0,0 +1,63 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.Tank;
public class SupportTankDTO {
private long id;
private String name;
private Long nationId;
private Long levelId;
private int cost;
public SupportTankDTO(){}
public SupportTankDTO(Tank tank){
this.id = tank.getId();
this.nationId = tank.getNation().getId();
this.levelId = tank.getLevel().getId();
this.name = tank.getName();
this.cost = tank.getCost();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Long getId(){
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getNationId(){
return nationId;
}
public void setNationId(Long nationId) {
this.nationId = nationId;
}
public Long getLevelId(){
return levelId;
}
public void setLevelId(Long levelId) {
this.levelId = levelId;
}
public int getCost(){
return cost;
}
public void setCost(int cost) {
this.cost = cost;
}
}

View File

@ -0,0 +1,63 @@
package premium_store.controller.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import premium_store.model.Tank;
public class TankDTO {
private long id;
private String name;
private SimpleNationDTO nation;
private LevelDTO level;
private int cost;
public TankDTO(){}
public TankDTO(Tank tank){
this.id = tank.getId();
this.nation = new SimpleNationDTO(tank.getNation());
this.level = new LevelDTO(tank.getLevel());
this.name = tank.getName();
this.cost = tank.getCost();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId(){
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public SimpleNationDTO getNation(){
return nation;
}
public void setNation(SimpleNationDTO nation) {
this.nation = nation;
}
public LevelDTO getLevel(){
return level;
}
public void setLevel(LevelDTO level) {
this.level = level;
}
public int getCost(){
return cost;
}
public void setCost(int cost) {
this.cost = cost;
}
}

View File

@ -0,0 +1,64 @@
package premium_store.controller.DTO;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class UserSignupDto {
@NotBlank
@Size(min = 3, max = 64)
private String login;
@NotBlank
@Size(min = 6, max = 64)
private String password;
@NotBlank
private String email;
@NotBlank
private String balance;
@NotBlank
@Size(min = 6, max = 64)
private String passwordConfirm;
public void setLogin(String login){
this.login = login;
}
public void setPassword(String password){
this.password = password;
}
public void setPasswordConfirm(String passwordConfirm){
this.passwordConfirm = passwordConfirm;
}
public void setEmail(String email) {
this.email = email;
}
public void setBalance(String balance) {
this.balance = balance;
}
public String getLogin(){
return login;
}
public String getPassword(){
return password;
}
public String getPasswordConfirm(){
return passwordConfirm;
}
public String getEmail() {
return email;
}
public String getBalance() {
return balance;
}
}

View File

@ -0,0 +1,97 @@
package premium_store.controller.controller;
import premium_store.configuration.OpenAPI30Configuration;
import premium_store.controller.DTO.ClientDTO;
import premium_store.controller.DTO.UserSignupDto;
import premium_store.model.GameClient;
import premium_store.model.UserRole;
import premium_store.service.GameClientService;
import premium_store.service.TankService;
import org.springframework.data.domain.Page;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.ValidationException;
import java.util.List;
import java.util.stream.IntStream;
@RestController
public class GameClientController {
public static final String URL_LOGIN = "/jwt/login";
public static final String URL_SING_UP = "/sing_up";
public static final String URL_WHO_AM_I = "/who_am_i";
private final GameClientService gameClientService;
private final TankService tankService;
public GameClientController(GameClientService gameClientService, TankService tankService){
this.gameClientService = gameClientService;
this.tankService = tankService;
}
@PostMapping(URL_LOGIN)
public String login(@RequestBody @Valid ClientDTO userDto) {
return gameClientService.loginAndGetToken(userDto);
}
@PostMapping(URL_SING_UP)
public String singUp(@RequestBody @Valid UserSignupDto userSignupDto) {
try {
final GameClient user = gameClientService.addClient(userSignupDto.getLogin(), userSignupDto.getEmail(), userSignupDto.getPassword(),
Integer.parseInt(userSignupDto.getBalance()), userSignupDto.getPasswordConfirm(), UserRole.USER);
return "created " + user.getLogin();
} catch (ValidationException e) {
return e.getMessage();
}
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping(OpenAPI30Configuration.API_PREFIX + "/")
public ClientDTO getClient(@PathVariable Long id) {
return new ClientDTO(gameClientService.findClient(id));
}
@PutMapping(OpenAPI30Configuration.API_PREFIX + "/{id}")
@Secured({UserRole.AsString.USER})
public ClientDTO updateClient(@PathVariable Long id,
@RequestParam("login") String login,
@RequestParam("password") String password,
@RequestParam("email") String email,
@RequestParam("balance") Integer balance,
@RequestParam("tankId") Long tankId) {
return new ClientDTO(gameClientService.updateClient(id, login, password, email, balance, tankService.findTank(tankId)));
}
@PostMapping(OpenAPI30Configuration.API_PREFIX + "/{id}")
@Secured({UserRole.AsString.USER})
public ClientDTO deleteClient(@PathVariable Long id) {
return new ClientDTO(gameClientService.deleteClient(id));
}
@GetMapping(OpenAPI30Configuration.API_PREFIX + "/users")
@Secured({UserRole.AsString.ADMIN})
public Pair<Page<ClientDTO>, List<Integer>> getClients(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "5") int size) {
final Page<ClientDTO> users = gameClientService.findAllPages(page, size)
.map(ClientDTO::new);
final int totalPages = users.getTotalPages();
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.toList();
return new Pair<>(users, pageNumbers);
}
@GetMapping(URL_WHO_AM_I)
public String whoAmI(@RequestParam("token") String token) {
UserDetails userDetails = gameClientService.loadUserByToken(token);
GameClient user = gameClientService.findByLogin(userDetails.getUsername());
return user.getRole().toString();
}
}

View File

@ -0,0 +1,141 @@
package premium_store.controller.controller;
import org.springframework.data.domain.Page;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.*;
import premium_store.model.UserRole;
import premium_store.service.GameClientService;
import premium_store.service.TankService;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;
import java.util.stream.IntStream;
@Controller
@RequestMapping("/clients")
public class GameClientMvcController {
private final GameClientService gameClientService;
private final TankService tankService;
public GameClientMvcController(GameClientService gameClientService, TankService tankService){
this.gameClientService = gameClientService;
this.tankService = tankService;
}
@GetMapping({"/showUpdate"})
public String showUpdateUserForm(Principal principal, Model model) {
ClientDTO clientDTO = new ClientDTO(gameClientService.findByLogin(principal.getName()));
model.addAttribute("clientDTO", clientDTO);
return "client-edit";
}
@GetMapping
@Secured({UserRole.AsString.ADMIN})
public String getClients(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "5") int size,
Principal principal, Model model) {
final Page<ClientDTO> users = gameClientService.findAllPages(page, size)
.map(ClientDTO::new);
model.addAttribute("users", users);
final int totalPages = users.getTotalPages();
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.toList();
model.addAttribute("pages", pageNumbers);
model.addAttribute("totalPages", totalPages);
return "clients";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editClient(@PathVariable(required = false) Long id, Model model) {
if(id == null || id <= 0){
model.addAttribute("supportClientDTO", new SupportClientDTO());
}
else {
model.addAttribute("clientId", id);
model.addAttribute("supportClientDTO", new SupportClientDTO(gameClientService.findClient(id)));
}
List<TankDTO> tanks = tankService.findAllTanks().stream()
.map(TankDTO::new)
.toList();
model.addAttribute("tanks", tanks);
/*if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "client-edit";
}
try {
gameClientService.updateClient(userDto);
@SuppressWarnings("unchecked")
Collection<SimpleGrantedAuthority> nowAuthorities =
(Collection<SimpleGrantedAuthority>) SecurityContextHolder.getContext()
.getAuthentication()
.getAuthorities();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDto.getLogin(), nowAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ValidationException e) {
model.addAttribute("errors", e.getMessage());
}*/
return "client-edit";
}
@GetMapping(value = {"/tanksOfClient", "/tanksOfClient/{id}"})
@Secured({UserRole.AsString.ADMIN})
public String editTanksOfClient(@PathVariable(required = false) Long id, Model model){
if(id == null || id <= 0){
model.addAttribute("clientDTO", new ClientDTO());
}
else {
model.addAttribute("clientId", id);
model.addAttribute("clientDTO", new ClientDTO(gameClientService.findClient(id)));
}
return "tanks-of-client-edit";
}
@PostMapping(value = {"", "/{id}"})
public String saveClient(@PathVariable(required = false) Long id,
@ModelAttribute @Valid SupportClientDTO clientDTO,
BindingResult bindingResult,
Model model){
if(bindingResult.hasErrors()){
model.addAttribute("errors", bindingResult.getAllErrors());
return "client-edit";
}
//тут два раза прокидываем пароль, так как метод требует на вход строку с подтверждением пароля
if(id == null || id <= 0){
gameClientService.addClient(clientDTO.getLogin(), clientDTO.getPassword(), clientDTO.getEmail(), clientDTO.getBalance(), clientDTO.getPassword(), clientDTO.getRole());
} else {
gameClientService.updateClient(clientDTO);
}
return "redirect:/clients";
}
@PostMapping("/delete/{id}")
@Secured({UserRole.AsString.ADMIN})
public String deleteClient(Principal principal, Model model,
@PathVariable Long id){
gameClientService.deleteClient(id);
return "redirect:/clients";
}
}

View File

@ -0,0 +1,67 @@
package premium_store.controller.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.LevelDTO;
import premium_store.service.TankLevelService;
import javax.validation.Valid;
@Controller
@RequestMapping("/level")
public class LevelMvcController {
private final TankLevelService levelService;
public LevelMvcController(TankLevelService levelService){
this.levelService = levelService;
}
@GetMapping
public String getLevels(Model model){
model.addAttribute("levels",
levelService.findAllLevels().stream()
.map(LevelDTO::new)
.toList());
return "level";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editLevel(@PathVariable(required = false) Long id, Model model){
if(id == null || id <= 0){
model.addAttribute("levelDTO", new LevelDTO());
}
else {
model.addAttribute("levelId", id);
model.addAttribute("levelDTO", new LevelDTO(levelService.findLevel(id)));
}
return "level-edit";
}
@PostMapping(value = {"", "/{id}"})
public String saveLevel(@PathVariable(required = false) Long id,
@ModelAttribute @Valid LevelDTO levelDTO,
BindingResult bindingResult,
Model model){
if(bindingResult.hasErrors()){
model.addAttribute("errors", bindingResult.getAllErrors());
return "level-edit";
}
if(id == null || id <= 0){
levelService.addLevel(levelDTO.getLevel());
} else {
levelService.updateLevel(id, levelDTO.getLevel());
}
return "redirect:/level";
}
@PostMapping("/delete/{id}")
public String deleteLevel(@PathVariable Long id){
levelService.deleteLevel(id);
return "redirect:/level";
}
}

View File

@ -0,0 +1,61 @@
package premium_store.controller.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import premium_store.configuration.OpenAPI30Configuration;
import premium_store.configuration.WebConfiguration;
import premium_store.controller.DTO.FullNationDTO;
import premium_store.model.UserRole;
import premium_store.service.NationService;
import premium_store.service.TankService;
import java.util.List;
//привязываем наш контроллер к придуманному корневому URL благодаря аннотациям
//здесь происходит внедрение зависимости нашего сервиса
//так же здесь прописываем вызовы методов CRUD в привязке к URL
@RestController
@CrossOrigin
@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/nation")
public class NationController {
private final NationService nationService;
private final TankService tankService;
public NationController(NationService nationService, TankService tankService){
this.nationService = nationService;
this.tankService = tankService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public FullNationDTO getNation(@PathVariable Long id) {
return new FullNationDTO(nationService.findNation(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты StudentDto
@GetMapping("/getNations")
public List<FullNationDTO> getNations() {
return nationService.findAllNations().stream()
.map(FullNationDTO::new)
.toList();
}
@PostMapping("/create")
@Secured({UserRole.AsString.ADMIN})
public FullNationDTO createNation(@RequestParam("nation") String nation) {
return new FullNationDTO(nationService.addNation(nation));
}
@PutMapping("/{id}")
public FullNationDTO updateNation(@PathVariable Long id,
@RequestParam("nation") String nation,
@RequestParam(value = "tankId", required = false) Long tankId ) {
return new FullNationDTO(nationService.updateNation(id, nation, (tankId > 0) ? tankService.findTank(tankId) : null));
}
@DeleteMapping("/{id}")
public FullNationDTO deleteNation(@PathVariable Long id) {
return new FullNationDTO(nationService.deleteNation(id));
}
}

View File

@ -0,0 +1,82 @@
package premium_store.controller.controller;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.FullNationDTO;
import premium_store.controller.DTO.SimpleNationDTO;
import premium_store.service.NationService;
import javax.validation.Valid;
@Controller
@RequestMapping("/nation")
public class NationMvcController {
private final NationService nationService;
public NationMvcController(NationService nationService){
this.nationService = nationService;
}
@GetMapping
public String getNations(Model model){
model.addAttribute("nations",
nationService.findAllNations().stream()
.map(FullNationDTO::new)
.toList());
return "nation";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editNation(@PathVariable(required = false) Long id, Model model){
if(id == null || id <= 0){
model.addAttribute("simpleNationDTO", new SimpleNationDTO());
}
else {
model.addAttribute("nationId", id);
model.addAttribute("simpleNationDTO", new SimpleNationDTO(nationService.findNation(id)));
}
return "nation-edit";
}
@GetMapping(value = {"/editTank", "/editTank/{id}"})
public String editTankNation(@PathVariable(required = false) Long id, Model model){
if(id == null || id <= 0){
model.addAttribute("fullNationDTO", new FullNationDTO());
}
else {
model.addAttribute("nationId", id);
model.addAttribute("fullNationDTO", new FullNationDTO(nationService.findNation(id)));
}
return "nation-tank-edit";
}
@PostMapping(value = {"", "/{id}"})
public String saveNation(@PathVariable(required = false) Long id,
@ModelAttribute @Valid SimpleNationDTO simpleNationDTO,
BindingResult bindingResult,
Model model){
if(bindingResult.hasErrors()){
model.addAttribute("errors", bindingResult.getAllErrors());
return "nation-edit";
}
if(id == null || id <= 0){
nationService.addNation(simpleNationDTO.getNation());
} else {
nationService.updateNation(id, simpleNationDTO.getNation(), null);
}
return "redirect:/nation";
}
@PostMapping("/delete/{id}")
public String deleteNation(@PathVariable Long id){
nationService.deleteNation(id);
return "redirect:/nation";
}
}

View File

@ -0,0 +1,19 @@
package premium_store.controller.controller;
public class Pair<F, S> {
private final F first;
private final S second;
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
public F getFirst() {
return first;
}
public S getSecond() {
return second;
}
}

View File

@ -0,0 +1,76 @@
package premium_store.controller.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import premium_store.configuration.OpenAPI30Configuration;
import premium_store.configuration.WebConfiguration;
import premium_store.controller.DTO.TankDTO;
import premium_store.model.UserRole;
import premium_store.service.NationService;
import premium_store.service.TankLevelService;
import premium_store.service.TankService;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/tank")
public class TankController {
private final TankService tankService;
private final TankLevelService tankLevelService;
private final NationService nationService;
public TankController(TankService tankService, TankLevelService tankLevelService, NationService nationService){
this.tankService = tankService;
this.tankLevelService = tankLevelService;
this.nationService = nationService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public TankDTO getTank(@PathVariable Long id) {
return new TankDTO(tankService.findTank(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты TankDTO
@GetMapping("/")
public List<TankDTO> getTanks() {
return tankService.findAllTanks().stream()
.map(TankDTO::new)
.toList();
}
@GetMapping("/filteredList/")
public List<TankDTO> getFilteredTanks(@RequestParam("nation") String nation,
@RequestParam("firstLevel") int firstLevel,
@RequestParam("secondLevel") int secondLevel) {
return tankService.findListTank(nation, firstLevel, secondLevel).stream()
.map(TankDTO::new)
.toList();
}
@PostMapping("/create")
@Secured({UserRole.AsString.ADMIN})
public TankDTO createTank(@RequestParam("firstName") String name,
@RequestParam("nationId") Long nationId,
@RequestParam("levelId") Long tankLevelId,
@RequestParam("cost") int cost
) {
return new TankDTO(tankService.addTank(name, nationService.findNation(nationId), tankLevelService.findLevel(tankLevelId), cost));
}
@PutMapping("/{id}")
public TankDTO updateTank(@PathVariable Long id,
@RequestParam("firstName") String name,
@RequestParam("nationId") Long nationId,
@RequestParam("levelId") Long tankLevelId,
@RequestParam("cost") int cost
) {
return new TankDTO(tankService.updateTank(id, name, nationService.findNation(nationId), tankLevelService.findLevel(tankLevelId), cost));
}
@DeleteMapping("/{id}")
public TankDTO deleteTank(@PathVariable Long id) {
return new TankDTO(tankService.deleteTank(id));
}
}

View File

@ -0,0 +1,52 @@
package premium_store.controller.controller;
import org.springframework.web.bind.annotation.*;
import premium_store.configuration.OpenAPI30Configuration;
import premium_store.configuration.WebConfiguration;
import premium_store.controller.DTO.LevelDTO;
import premium_store.service.TankLevelService;
import java.util.List;
//привязываем наш контроллер к придуманному корневому URL благодаря аннотациям
//здесь происходит внедрение зависимости нашего сервиса
//так же здесь прописываем вызовы методов CRUD в привязке к URL
@RestController
@RequestMapping(OpenAPI30Configuration.API_PREFIX + "/level")
public class TankLevelController {
private final TankLevelService tankLevelService;
public TankLevelController(TankLevelService tankLevelService){
this.tankLevelService = tankLevelService;
}
//аннотация PathVariable связывает значения id из URL и Long id
@GetMapping("/{id}")
public LevelDTO getLevel(@PathVariable Long id) {
return new LevelDTO(tankLevelService.findLevel(id));
}
//с помощью Java Stream преобразуем набор пришедших данных в объекты StudentDto
@GetMapping("/getLevels")
public List<LevelDTO> getLevels() {
return tankLevelService.findAllLevels().stream()
.map(LevelDTO::new)
.toList();
}
@PostMapping("/")
public LevelDTO createLevel(@RequestParam("Level") int level) {
return new LevelDTO(tankLevelService.addLevel(level));
}
@PutMapping("/{id}")
public LevelDTO updateLevel(@PathVariable Long id,
@RequestParam("Level") int level) {
return new LevelDTO(tankLevelService.updateLevel(id, level));
}
@DeleteMapping("/{id}")
public LevelDTO deleteLevel(@PathVariable Long id) {
return new LevelDTO(tankLevelService.deleteLevel(id));
}
}

View File

@ -0,0 +1,113 @@
package premium_store.controller.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import premium_store.controller.DTO.*;
import premium_store.service.NationService;
import premium_store.service.TankLevelService;
import premium_store.service.TankService;
import javax.validation.Valid;
import java.util.List;
@Controller
@RequestMapping("/tank")
public class TankMvcController {
private final TankService tankService;
private final NationService nationService;
private final TankLevelService tankLevelService;
public TankMvcController(TankService tankService, NationService nationService, TankLevelService tankLevelService){
this.tankService = tankService;
this.nationService = nationService;
this.tankLevelService = tankLevelService;
}
@GetMapping
public String getTanks(Model model){
model.addAttribute("tanks",
tankService.findAllTanks().stream()
.map(TankDTO::new)
.toList());
List<SimpleNationDTO> nations = nationService.findAllNations().stream()
.map(SimpleNationDTO::new)
.toList();
model.addAttribute("nations", nations);
List<LevelDTO> levels = tankLevelService.findAllLevels().stream()
.map(LevelDTO::new)
.toList();
model.addAttribute("levels", levels);
model.addAttribute("sortDTO", new SortDTO());
return "tank";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editTank(@PathVariable(required = false) Long id, Model model){
if(id == null || id <= 0){
model.addAttribute("supportTankDTO", new SupportTankDTO());
}
else {
model.addAttribute("tankId", id);
model.addAttribute("supportTankDTO", new SupportTankDTO(tankService.findTank(id)));
}
List<SimpleNationDTO> nations = nationService.findAllNations().stream()
.map(SimpleNationDTO::new)
.toList();
model.addAttribute("nations", nations);
List<LevelDTO> levels = tankLevelService.findAllLevels().stream()
.map(LevelDTO::new)
.toList();
model.addAttribute("levels", levels);
return "tank-edit";
}
@GetMapping("/filteredList")
public String getFilteredTanks(@ModelAttribute SortDTO sortDTO,
Model model) {
List<TankDTO> tanks = tankService.findListTank(sortDTO.getNation(), sortDTO.getFirstLevel(), sortDTO.getSecondLevel()).stream()
.map(TankDTO::new)
.toList();
model.addAttribute("tanks", tanks);
return "filter";
}
@PostMapping(value = {"", "/{id}"})
public String saveTank(@PathVariable(required = false) Long id,
@ModelAttribute @Valid SupportTankDTO tankDTO,
BindingResult bindingResult,
Model model){
if(bindingResult.hasErrors()){
model.addAttribute("errors", bindingResult.getAllErrors());
return "tank-edit";
}
if(id == null || id <= 0){
tankService.addTank(tankDTO);
} else {
tankService.updateTank(tankDTO);
}
return "redirect:/tank";
}
@PostMapping("/delete/{id}")
public String deleteTank(@PathVariable Long id){
tankService.deleteTank(id);
return "redirect:/tank";
}
}

View File

@ -0,0 +1,51 @@
package premium_store.controller.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import premium_store.controller.DTO.UserSignupDto;
import premium_store.model.GameClient;
import premium_store.model.UserRole;
import premium_store.service.GameClientService;
import javax.validation.Valid;
import javax.validation.ValidationException;
@Controller
@RequestMapping(UserSignupMvcController.SIGNUP_URL)
public class UserSignupMvcController {
public static final String SIGNUP_URL = "/signup";
private final GameClientService clientService;
public UserSignupMvcController(GameClientService clientService) {
this.clientService = clientService;
}
@GetMapping
public String showSignupForm(Model model) {
model.addAttribute("clientDTO", new UserSignupDto());
return "signup";
}
@PostMapping
public String signup(@ModelAttribute("clientDTO") @Valid UserSignupDto userSignupDto,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "signup";
}
try {
final GameClient client = clientService.addClient(userSignupDto.getLogin(), userSignupDto.getEmail(), userSignupDto.getPassword(), Integer.parseInt(userSignupDto.getBalance()), userSignupDto.getPasswordConfirm(), UserRole.USER);
return "redirect:/login?created=" + client.getLogin();
} catch (ValidationException e) {
model.addAttribute("errors", e.getMessage());
return "signup";
}
}
}

Some files were not shown because too many files have changed in this diff Show More