Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
4d04a460f9 | |||
db261b7ffc | |||
32a50dda13 | |||
f8ab43a010 | |||
9938451ac9 | |||
c3cfcecc0b | |||
acb61a9a8e | |||
ec634ba472 | |||
f9c5202f6b | |||
f024215f82 | |||
2105cff6f9 | |||
9730c77ed5 | |||
eb03fcc404 | |||
2683ba9907 | |||
92e9b29a1e | |||
d90389cfbe | |||
70ec81dc5c | |||
eab61da355 | |||
6dfb0655bb | |||
f0fec374ff | |||
a6d16481da | |||
815967146e |
27
build.gradle
27
build.gradle
@ -1,10 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '2.7.8'
|
id 'org.springframework.boot' version '3.0.2'
|
||||||
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
|
id 'io.spring.dependency-management' version '1.1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'ru.ulstu.is'
|
group = 'com.example'
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
@ -14,6 +14,27 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-devtools'
|
||||||
|
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
|
||||||
|
|
||||||
|
implementation 'org.webjars:bootstrap:5.1.3'
|
||||||
|
implementation 'org.webjars:jquery:3.6.0'
|
||||||
|
implementation 'org.webjars:font-awesome:6.1.0'
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'com.h2database:h2:2.1.210'
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||||
|
implementation 'com.auth0:java-jwt:4.4.0'
|
||||||
|
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
|
||||||
|
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||||
|
|
||||||
|
implementation 'org.hibernate.validator:hibernate-validator'
|
||||||
|
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31206
front/package-lock.json
generated
Normal file
31206
front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
front/package.json
Normal file
49
front/package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "pages_react",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.2.1",
|
||||||
|
"axios": "^1.1.3",
|
||||||
|
"bootstrap": "^5.2.3",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-bootstrap": "^2.7.2",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router": "^6.10.0",
|
||||||
|
"react-router-dom": "^6.6.1",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.24",
|
||||||
|
"@types/react-dom": "^18.0.8",
|
||||||
|
"@vitejs/plugin-react": "^2.2.0",
|
||||||
|
"json-server": "^0.17.1",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"vite": "^3.2.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
14
front/public/index.html
Normal file
14
front/public/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/node_modules/@fortawesome/fontawesome-free/css/all.min.css">
|
||||||
|
<title>Сеть автошкол</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
65
front/src/App.js
Normal file
65
front/src/App.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRoutes, Outlet, BrowserRouter } from 'react-router-dom';
|
||||||
|
import Students from './components/Students.jsx';
|
||||||
|
import Header from "./components/commons/Header.jsx"
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import DrivingSchools from './components/DrivingSchools.jsx';
|
||||||
|
import Categories from './components/Categories.jsx';
|
||||||
|
import OneDrivingSchool from './components/OneDrivingSchool.jsx';
|
||||||
|
import CountStudInCategory from './components/CountStudInCategory.jsx';
|
||||||
|
import Login from './components/Login.jsx';
|
||||||
|
import Logout from './components/Logout.jsx';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function Router(props) {
|
||||||
|
return useRoutes(props.rootRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const routes = [
|
||||||
|
{ index: true, element: <DrivingSchools /> },
|
||||||
|
{ path: '/', element: <DrivingSchools />, label: 'Сеть Автошкол' },
|
||||||
|
{ path: '/drivingSchools', element: <DrivingSchools />, label: 'Автошколы' },
|
||||||
|
{ path: '/students', element: <Students />, label: 'Студенты' },
|
||||||
|
{ path: '/categories', element: <Categories />, label: 'Категории' },
|
||||||
|
{ path: '/studcategory', element: <CountStudInCategory />, label: 'Количество студентов в категории' },
|
||||||
|
{ path: '/drivingSchool/:id', element: <OneDrivingSchool />},
|
||||||
|
{ path: '/login', element: <Login />},
|
||||||
|
{ path: '/logout', element: <Logout />},
|
||||||
|
];
|
||||||
|
const links = routes.filter(route => route.hasOwnProperty('label'));
|
||||||
|
const [token, setToken] = useState(localStorage.getItem('token'));
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
function handleStorageChange() {
|
||||||
|
setToken(localStorage.getItem('token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('storage', handleStorageChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('storage', handleStorageChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const rootRoute = [
|
||||||
|
{ path: '/', element: render(links), children: routes }
|
||||||
|
];
|
||||||
|
function render(links) {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<Header token={token} links={links} />
|
||||||
|
<div className="w-100">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Router rootRoute={ rootRoute } />
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
56
front/src/components/Catalog.jsx
Normal file
56
front/src/components/Catalog.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import Table from "./commons/Table.jsx";
|
||||||
|
import { useState} from 'react';
|
||||||
|
import ModalForm from './commons/ModalForm.jsx';
|
||||||
|
// это абстрактный компонент для всех справочников
|
||||||
|
export default function Catalog(props) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [modalTitle, setModalTitle] = useState("");
|
||||||
|
|
||||||
|
const handleClose = () => setShow(false);
|
||||||
|
const handleShow = () => setShow(true);
|
||||||
|
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
setModalTitle("Добавление");
|
||||||
|
props.onBtnAdd();
|
||||||
|
handleShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChoose(itemId) {
|
||||||
|
setModalTitle("Выбрать");
|
||||||
|
props.onChoose(itemId);
|
||||||
|
handleShow();
|
||||||
|
}
|
||||||
|
function handleEdit(itemId) {
|
||||||
|
setModalTitle("Редактирование");
|
||||||
|
props.onEdit(itemId);
|
||||||
|
handleShow();
|
||||||
|
}
|
||||||
|
function handleRemove(item) {
|
||||||
|
props.onDelete(item);
|
||||||
|
}
|
||||||
|
function changeData(event) {
|
||||||
|
props.onFormChanged(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div>{props.name}</div>
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="success" onClick={handleAdd}>Добавить</Button>}
|
||||||
|
<Table
|
||||||
|
headers={props.headers}
|
||||||
|
items={props.items}
|
||||||
|
onChoose={handleChoose}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleRemove}
|
||||||
|
/>
|
||||||
|
<ModalForm
|
||||||
|
show={show}
|
||||||
|
onClose={handleClose}
|
||||||
|
modalTitle={modalTitle}
|
||||||
|
// onSubmit={submitForm}
|
||||||
|
onChange={changeData}
|
||||||
|
form={props.form}
|
||||||
|
/></>
|
||||||
|
|
||||||
|
}
|
12
front/src/components/CatalogForGroup.jsx
Normal file
12
front/src/components/CatalogForGroup.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import TableForGroup from "./commons/TableForGroup.jsx";
|
||||||
|
// это абстрактный компонент для всех справочников
|
||||||
|
export default function CatalogForGroup(props) {
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div>{props.name}</div>
|
||||||
|
<TableForGroup
|
||||||
|
headers={props.headers}
|
||||||
|
items={props.items}
|
||||||
|
/></>
|
||||||
|
|
||||||
|
}
|
49
front/src/components/CatalogSC.jsx
Normal file
49
front/src/components/CatalogSC.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import TableSC from "./commons/TableSC.jsx";
|
||||||
|
import { useState} from 'react';
|
||||||
|
import ModalForm from './commons/ModalForm.jsx';
|
||||||
|
// это абстрактный компонент для всех справочников
|
||||||
|
export default function CatalogSC(props) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [modalTitle, setModalTitle] = useState("");
|
||||||
|
|
||||||
|
const handleClose = () => setShow(false);
|
||||||
|
const handleShow = () => setShow(true);
|
||||||
|
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
setModalTitle("Добавление");
|
||||||
|
props.onBtnAdd();
|
||||||
|
handleShow();
|
||||||
|
}
|
||||||
|
function handleEdit(itemId) {
|
||||||
|
setModalTitle("Редактирование");
|
||||||
|
props.onEdit(itemId);
|
||||||
|
handleShow();
|
||||||
|
}
|
||||||
|
function handleRemove(item) {
|
||||||
|
props.onDelete(item);
|
||||||
|
}
|
||||||
|
function changeData(event) {
|
||||||
|
props.onFormChanged(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div>{props.name}</div>
|
||||||
|
<Button variant="success" onClick={handleAdd}>Добавить</Button>
|
||||||
|
<TableSC
|
||||||
|
headers={props.headers}
|
||||||
|
items={props.items}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleRemove}
|
||||||
|
/>
|
||||||
|
<ModalForm
|
||||||
|
show={show}
|
||||||
|
onClose={handleClose}
|
||||||
|
modalTitle={modalTitle}
|
||||||
|
// onSubmit={submitForm}
|
||||||
|
onChange={changeData}
|
||||||
|
form={props.form}
|
||||||
|
/></>
|
||||||
|
|
||||||
|
}
|
110
front/src/components/Categories.jsx
Normal file
110
front/src/components/Categories.jsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import Category from "../models/Category";
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import DataService from '../services/DataService';
|
||||||
|
import CatalogSC from "./CatalogSC.jsx";
|
||||||
|
import withAuth from './withAuth';
|
||||||
|
|
||||||
|
function Categories(props) {
|
||||||
|
const headers = [
|
||||||
|
{name: 'name', label: "Название"},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nameCatalog = "Категории";
|
||||||
|
const url = '/category';
|
||||||
|
const requestParams = '?name=nameData';
|
||||||
|
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [data, setData] = useState(new Category());
|
||||||
|
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
DataService.readAll(url, (data) => new Category(data))
|
||||||
|
.then(data => setItems(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
DataService.create(url +requestParams
|
||||||
|
.replace("nameData", data.name))
|
||||||
|
.then(() => loadItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(editedId) {
|
||||||
|
DataService.read(url + "/" + editedId, (e) => new Category(e))
|
||||||
|
.then(data => {
|
||||||
|
setData(new Category(data));
|
||||||
|
});
|
||||||
|
setEditing(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
function handleEditIsDone() {
|
||||||
|
DataService.update(url + "/" + data.id + requestParams
|
||||||
|
.replace("nameData", data.name)).then(() => loadItems());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(item) {
|
||||||
|
if (window.confirm('Удалить выбранный элемент?')) {
|
||||||
|
const promises = [];
|
||||||
|
promises.push(DataService.delete(url + "/" + item));
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
loadItems();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// при каждом изменении поля формы происходит вызов этого метода, обновляя данные объекта
|
||||||
|
function handleFormChange(event) {
|
||||||
|
setData({ ...data, [event.target.name]: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// определяет действия формы по нажатию Отправить
|
||||||
|
function submitForm() {
|
||||||
|
if (!isEditing) {
|
||||||
|
// если добавление элемента
|
||||||
|
handleAdd();
|
||||||
|
} else {
|
||||||
|
// если редактирование элемента;
|
||||||
|
handleEditIsDone();
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// вызывается при закрытии модального окна или по нажатию кнопки Добавить и очищает данные объекта
|
||||||
|
function reset() {
|
||||||
|
setData(new Category());
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// для каждого типа сущности своя форма,
|
||||||
|
// которая передается дальше в абстрактный компонент Catalog в качестве props.form
|
||||||
|
const form = <Form onSubmit={submitForm}>
|
||||||
|
<Form.Group className="mb-3" controlId="name">
|
||||||
|
<Form.Label>Название</Form.Label>
|
||||||
|
<Form.Control name="name" value={data.name} type="input" placeholder="Enter text" onChange={handleFormChange} required/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<CatalogSC name={nameCatalog}
|
||||||
|
headers={headers}
|
||||||
|
items={items}
|
||||||
|
onAdd={handleAdd}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onClose={reset}
|
||||||
|
onBtnAdd={reset}
|
||||||
|
form={form}
|
||||||
|
role={props.role}>
|
||||||
|
</CatalogSC>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
export default withAuth(Categories);
|
37
front/src/components/CountStudInCategory.jsx
Normal file
37
front/src/components/CountStudInCategory.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import DataService from '../services/DataService';
|
||||||
|
import CatalogForGroup from "./CatalogForGroup.jsx";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import GroupedStudAndCategoryDto from "../models/GroupedStudAndCategoryDto";
|
||||||
|
|
||||||
|
export default function CountStudInCategory(props) {
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{name: 'categoryName', label: "Название"},
|
||||||
|
{name: 'studentsCount', label: "Студенты"},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nameCatalog = "Количество студентов в категории";
|
||||||
|
|
||||||
|
const url = '/categoryStudent/groupbycategory';
|
||||||
|
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
DataService.readAll(url, (data) => new GroupedStudAndCategoryDto(data))
|
||||||
|
.then(data => setItems(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<CatalogForGroup name={nameCatalog}
|
||||||
|
headers={headers}
|
||||||
|
items={items}>
|
||||||
|
</CatalogForGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
155
front/src/components/DrivingSchools.jsx
Normal file
155
front/src/components/DrivingSchools.jsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import DataService from '../services/DataService';
|
||||||
|
import Catalog from "./Catalog.jsx";
|
||||||
|
import DrivingSchool from "../models/DrivingSchool";
|
||||||
|
import ModalForm from './commons/ModalForm';
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import withAuth from './withAuth';
|
||||||
|
|
||||||
|
function DrivingSchools(props) {
|
||||||
|
const headers = [
|
||||||
|
{name: 'name', label: "Название"},
|
||||||
|
{name: 'countStudents', label: "Студенты"},
|
||||||
|
];
|
||||||
|
const nameCatalog = "Автошколы";
|
||||||
|
|
||||||
|
const url = '/drivingSchool';
|
||||||
|
const requestParams = '?name=nameData';
|
||||||
|
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
const [data, setData] = useState(new DrivingSchool());
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [selectedId, setSelectedId] = useState(null);
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
DataService.readAll(url, (data) => new DrivingSchool(data))
|
||||||
|
.then(data => setItems(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
DataService.create(url +requestParams
|
||||||
|
.replace("nameData", data.name))
|
||||||
|
.then(() => loadItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(editedId) {
|
||||||
|
DataService.read(url + "/" + editedId, (e) => new DrivingSchool(e))
|
||||||
|
.then(data => {
|
||||||
|
setData(new DrivingSchool(data));
|
||||||
|
});
|
||||||
|
setEditing(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
function handleEditIsDone() {
|
||||||
|
|
||||||
|
DataService.update(url + "/" + data.id + requestParams
|
||||||
|
.replace("nameData", data.name))
|
||||||
|
.then(() => loadItems());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(item) {
|
||||||
|
if (window.confirm('Удалить выбранный элемент?')) {
|
||||||
|
const promises = [];
|
||||||
|
promises.push(DataService.delete(url + "/" + item));
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
loadItems();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// при каждом изменении поля формы происходит вызов этого метода, обновляя данные объекта
|
||||||
|
function handleFormChange(event) {
|
||||||
|
setData({ ...data, [event.target.name]: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// определяет действия формы по нажатию Отправить
|
||||||
|
function submitForm() {
|
||||||
|
if (!isEditing) {
|
||||||
|
// если добавление элемента
|
||||||
|
handleAdd();
|
||||||
|
} else {
|
||||||
|
// если редактирование элемента;
|
||||||
|
handleEditIsDone();
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// вызывается при закрытии модального окна или по нажатию кнопки Добавить и очищает данные объекта
|
||||||
|
function reset() {
|
||||||
|
setData(new DrivingSchool());
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// для каждого типа сущности своя форма,
|
||||||
|
// которая передается дальше в абстрактный компонент Catalog в качестве props.form
|
||||||
|
const form = <Form onSubmit={submitForm}>
|
||||||
|
<Form.Group className="mb-3" controlId="name">
|
||||||
|
<Form.Label>Название</Form.Label>
|
||||||
|
<Form.Control name="name" value={data.name} type="input" placeholder="Enter text" onChange={handleFormChange} required/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>;
|
||||||
|
|
||||||
|
|
||||||
|
const [showModalForm, setShowChoosing] = useState(false);
|
||||||
|
const formChooseDrivingSchool = <Form onSubmit={redirectToDrivingSchool}>
|
||||||
|
<Form.Group className="mb-3" controlId="name">
|
||||||
|
<Form.Label>Автошкола</Form.Label>
|
||||||
|
<Form.Select name="name_select" type="input" onChange={(e) => {setChosenDrivingSchool(e.target.value)}} required>
|
||||||
|
<option selected disabled>Выберите автошколу</option>
|
||||||
|
{
|
||||||
|
items.map((drivingSchool) => <option key={`drivingSchool_${drivingSchool.id}`} value={`${drivingSchool.id}`}>{`${drivingSchool.name}`}</option>)
|
||||||
|
}
|
||||||
|
</Form.Select>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Перейти
|
||||||
|
</Button>
|
||||||
|
</Form>;
|
||||||
|
|
||||||
|
function showModalFormChoosing() {
|
||||||
|
setShowChoosing(true);
|
||||||
|
}
|
||||||
|
function unshowModalFormChoosing() {
|
||||||
|
setShowChoosing(false);
|
||||||
|
}
|
||||||
|
const [chosenDrivingSchool, setChosenDrivingSchool] = useState(0);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
function redirectToDrivingSchool(item) {
|
||||||
|
setSelectedId(item);
|
||||||
|
navigate(`/drivingSchool/${item}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<ModalForm show={showModalForm} onClose={unshowModalFormChoosing} modalTitle={"Выбор автошколы"} form={formChooseDrivingSchool}></ModalForm>
|
||||||
|
<Catalog name={nameCatalog}
|
||||||
|
headers={headers}
|
||||||
|
items={items}
|
||||||
|
onAdd={handleAdd}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onChoose={redirectToDrivingSchool}
|
||||||
|
onClose={reset}
|
||||||
|
onBtnAdd={reset}
|
||||||
|
form={form}>
|
||||||
|
</Catalog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export default withAuth(DrivingSchools);
|
21
front/src/components/Login.css
Normal file
21
front/src/components/Login.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0069d9;
|
||||||
|
border-color: #0062cc;
|
||||||
|
}
|
70
front/src/components/Login.jsx
Normal file
70
front/src/components/Login.jsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import './Login.css';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import { useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
|
export default function Login(props) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loginData, setLoginData] = useState({ login: '', password: '' });
|
||||||
|
const hostURL = 'http://localhost:8080';
|
||||||
|
const handleLoginSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
login(loginData.login, loginData.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
let jwtData = result.split('.')[1]
|
||||||
|
let decodedJwtJsonData = window.atob(jwtData);
|
||||||
|
let decodedJwtData = JSON.parse(decodedJwtJsonData);
|
||||||
|
|
||||||
|
let role = decodedJwtData.role;
|
||||||
|
localStorage.setItem("role", role.toUpperCase());
|
||||||
|
window.dispatchEvent(new Event("storage"));
|
||||||
|
navigate("/");
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
localStorage.removeItem("role");
|
||||||
|
alert(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container-fluid">
|
||||||
|
<div className="row justify-content-center align-items-center vh-100">
|
||||||
|
<div className="col-sm-6 col-md-4">
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">Авторизация</h5>
|
||||||
|
<form onSubmit={handleLoginSubmit}>
|
||||||
|
<div className="form-group mb-3">
|
||||||
|
<label htmlFor="login">Логин</label>
|
||||||
|
<input type="text" className="form-control" id="login" value={loginData.login} onChange={(e) => setLoginData({ ...loginData, login: e.target.value })} />
|
||||||
|
</div>
|
||||||
|
<div className="form-group mb-3">
|
||||||
|
<label htmlFor="loginPassword">Пароль</label>
|
||||||
|
<input type="password" className="form-control" id="loginPassword" value={loginData.password} onChange={(e) => setLoginData({ ...loginData, password: e.target.value })} />
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="btn btn-primary">Вход</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
20
front/src/components/Logout.jsx
Normal file
20
front/src/components/Logout.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
function Logout() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Удаление токена из localStorage или другого места
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
localStorage.removeItem("role");
|
||||||
|
window.dispatchEvent(new Event("storage"));
|
||||||
|
// Перенаправление пользователя на страницу входа или другую страницу
|
||||||
|
navigate('/login');
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logout;
|
258
front/src/components/OneDrivingSchool.jsx
Normal file
258
front/src/components/OneDrivingSchool.jsx
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import DrivingSchool from "../models/DrivingSchool";
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
import DataService from '../services/DataService';
|
||||||
|
import Student from '../models/Student';
|
||||||
|
import Category from '../models/Category';
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import ModalForm from './commons/ModalForm';
|
||||||
|
import styles from "./OneDrivingSchool.module.css";
|
||||||
|
import withAuth from './withAuth';
|
||||||
|
|
||||||
|
function OneDrivingSchool(props) {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const url = '/drivingSchool/id';
|
||||||
|
const urlDrivingSchoolStud = '/drivingSchool/id/students';
|
||||||
|
const urlStud = '/student/free';
|
||||||
|
const urlCat = '/category';
|
||||||
|
const urlHire = '/drivingSchool/id/hire';
|
||||||
|
const urlDismiss = '/drivingSchool/id/dismiss';
|
||||||
|
const urlAddCat = '/student/id/addCat';
|
||||||
|
const urlDelCat = '/student/id/delCat';
|
||||||
|
const requestParamsCategory = '?category=catId';
|
||||||
|
const requestParamsHire = '?studentId=studId';
|
||||||
|
|
||||||
|
const [drivingSchool, setDrivingSchool] = useState(new DrivingSchool());
|
||||||
|
const [itemsStudFree, setItemsStudFree] = useState([]);
|
||||||
|
const [itemsStudDrivingSchool, setItemsStudDrivingSchool] = useState([]);
|
||||||
|
const [itemsCat, setItemsCat] = useState([]);
|
||||||
|
|
||||||
|
const headersStud = [
|
||||||
|
{name: 'surname', label: "Фамилия"},
|
||||||
|
{name: 'name', label: "Имя"},
|
||||||
|
{name: 'categoriesString', label: 'Категории'},
|
||||||
|
{name: 'phoneNumber', label: "Номер телефона"}
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDrivingSchool();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function loadDrivingSchool() {
|
||||||
|
DataService.read(url.replace("id", id), (data) => new DrivingSchool(data)).then((data) => setDrivingSchool(new DrivingSchool(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItemsStudents() {
|
||||||
|
DataService.readAll(urlStud, (data) => new Student(data))
|
||||||
|
.then(data => setItemsStudFree(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItemsCategories() {
|
||||||
|
DataService.readAll(urlCat, (data) => new Category(data))
|
||||||
|
.then(data => setItemsCat(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItemsStudentsDrivingSchool() {
|
||||||
|
DataService.readAll(urlDrivingSchoolStud.replace("id", drivingSchool.id), (data) => new Student(data))
|
||||||
|
.then(data => setItemsStudDrivingSchool(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// логика для найма или увольнения
|
||||||
|
const [isShowHire, setShowHire] = useState(false);
|
||||||
|
|
||||||
|
function showModalFormHire() {
|
||||||
|
loadItemsStudents();
|
||||||
|
setShowHire(true);
|
||||||
|
}
|
||||||
|
function unshowModalFormHire() {
|
||||||
|
setShowHire(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isShowDismiss, setShowDismiss] = useState(false);
|
||||||
|
|
||||||
|
function showModalFormDismiss(e) {
|
||||||
|
loadItemsStudentsDrivingSchool();
|
||||||
|
setShowDismiss(true);
|
||||||
|
}
|
||||||
|
function unshowModalFormDismiss() {
|
||||||
|
setShowDismiss(false);
|
||||||
|
}
|
||||||
|
const [student, setStudent] = useState(new Student());
|
||||||
|
|
||||||
|
function studentChosenFree(e) {
|
||||||
|
setStudent(itemsStudFree.filter((stud) => stud.id == e.target.value)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function studentChosenBusy(e) {
|
||||||
|
setStudent(Array.from(drivingSchool.students).filter((stud) => stud.id == e.target.value)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formHireSubmit() {
|
||||||
|
DataService.update(urlHire.replace("id",drivingSchool.id) + requestParamsHire
|
||||||
|
.replace("studId", student.id))
|
||||||
|
.then(() => loadDrivingSchool());
|
||||||
|
|
||||||
|
|
||||||
|
setStudent(new Student());
|
||||||
|
}
|
||||||
|
|
||||||
|
function formDismissSubmit() {
|
||||||
|
DataService.update(urlDismiss.replace("id",drivingSchool.id) + requestParamsHire
|
||||||
|
.replace("studId", student.id))
|
||||||
|
.then(() => loadDrivingSchool());
|
||||||
|
|
||||||
|
setStudent(new Student());
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isShowChooseCategory, setShowChooseCategory] = useState(false);
|
||||||
|
|
||||||
|
function showModalFormChooseCategory() {
|
||||||
|
loadItemsCategories();
|
||||||
|
loadItemsStudentsDrivingSchool();
|
||||||
|
setShowChooseCategory(true);
|
||||||
|
}
|
||||||
|
function unshowModalFormChooseCategory() {
|
||||||
|
setShowChooseCategory(false);
|
||||||
|
setCategoryStud([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [categoriesChosen, setCategoryStud] = useState([]);
|
||||||
|
function checkBoxChanged(e) {
|
||||||
|
// если чекбокс был выбран, то добавляем в массив категорию
|
||||||
|
if (e.target.checked)
|
||||||
|
{
|
||||||
|
categoriesChosen.push(e.target.value);
|
||||||
|
} // если чекбокс был убран, то исключаем его значение из массива
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let i = categoriesChosen.indexOf(e.target.value);
|
||||||
|
if(i >= 0) {
|
||||||
|
categoriesChosen.splice(i,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formChooseCategoriesSubmit() {
|
||||||
|
for (let i = 0; i < categoriesChosen.length; i++) {
|
||||||
|
DataService.update(urlAddCat.replace("id",student.id) + requestParamsCategory
|
||||||
|
.replace("catId", categoriesChosen[i]))
|
||||||
|
.then(() => loadDrivingSchool());
|
||||||
|
}
|
||||||
|
|
||||||
|
let categoriesStud = student.categories;
|
||||||
|
|
||||||
|
for (let i = 0; i < categoriesStud.length; i++) {
|
||||||
|
if (categoriesChosen.indexOf(''+categoriesStud[i].id) == -1) {
|
||||||
|
// удаление категории
|
||||||
|
DataService.update(urlDelCat.replace("id",student.id) + requestParamsCategory
|
||||||
|
.replace("catId", categoriesStud[i].id))
|
||||||
|
.then(() => loadDrivingSchool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formHire = <Form onSubmit={formHireSubmit}>
|
||||||
|
<Form.Group className="mb-3" controlId="student">
|
||||||
|
<Form.Label>Студент</Form.Label>
|
||||||
|
<Form.Select name="student_select" type="input" onChange={studentChosenFree} required>
|
||||||
|
<option selected disabled>Выберите студента</option>
|
||||||
|
{
|
||||||
|
itemsStudFree.map((e) => <option key={`stud_${e.id}`} value={`${e.id}`}>{`${e.surname} ${e.name}`}</option>)
|
||||||
|
}
|
||||||
|
</Form.Select>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>;
|
||||||
|
|
||||||
|
|
||||||
|
const formDismiss = <Form onSubmit={formDismissSubmit}>
|
||||||
|
<Form.Group className="mb-3" controlId="student">
|
||||||
|
<Form.Label>Студент</Form.Label>
|
||||||
|
<Form.Select name="student_select" type="input" onChange={studentChosenBusy} required>
|
||||||
|
<option selected disabled>Выберите студента</option>
|
||||||
|
{
|
||||||
|
itemsStudDrivingSchool.map((e) => <option key={`stud_${e.id}`} value={`${e.id}`}>{`${e.surname} ${e.name}`}</option>)
|
||||||
|
}
|
||||||
|
</Form.Select>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>;
|
||||||
|
|
||||||
|
const formCheckBoxesCategory = <Form onSubmit={formChooseCategoriesSubmit}>
|
||||||
|
<Form.Group className="mb-3" controlId="student">
|
||||||
|
<Form.Label>Студент</Form.Label>
|
||||||
|
<Form.Select name="student_select" type="input" onChange={studentChosenBusy} required>
|
||||||
|
<option selected disabled>Выберите студента</option>
|
||||||
|
{
|
||||||
|
itemsStudDrivingSchool.map((e) => <option key={`stud_${e.id}`} value={`${e.id}`}>{`${e.surname} ${e.name}`}</option>)
|
||||||
|
}
|
||||||
|
</Form.Select>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3" controlId="category">
|
||||||
|
<Form.Label>Категории</Form.Label>
|
||||||
|
<div className={`${styles.prokrutka}`}>
|
||||||
|
{
|
||||||
|
itemsCat.map((p) => <div style={{width: `150 px`}}>
|
||||||
|
<input type="checkbox" key={`${p.id}`} value={`${p.id}`} onChange={checkBoxChanged}/>
|
||||||
|
{/* {!studentHasCategory(p) && <input type="checkbox" key={`${p.id}`} value={`${p.id}`} onChange={checkBoxChanged}/>}
|
||||||
|
{studentHasCategory(p) && <input type="checkbox" key={`${p.id}`} value={`${p.id}`} checked onChange={checkBoxChanged}/>} */}
|
||||||
|
{`${p.name}`}
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
if (!drivingSchool) {
|
||||||
|
return <div><h1>Автошкола не найдена</h1>
|
||||||
|
<Link to='/drivingSchools'>
|
||||||
|
<Button variant="info">Назад</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<Link to='/drivingSchools'>
|
||||||
|
<Button variant="info">Назад</Button>
|
||||||
|
</Link>
|
||||||
|
<h1>Название: {drivingSchool.name}</h1>
|
||||||
|
<h2>Количество студентов: {drivingSchool.countStudents}</h2>
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button name="Зачисление" onClick={showModalFormHire} variant="btn btn-outline-success">Зачислить студента</Button>}
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button name='Отчисление' onClick={showModalFormDismiss} variant="btn btn-outline-danger">Отчислить студента</Button>}
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button name='Выбор категории' onClick={showModalFormChooseCategory} variant="btn btn-outline-primary">Выбор категории</Button>}
|
||||||
|
<div >
|
||||||
|
<table className={`table table-hover`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
headersStud.map((header) => <th key={header.name}>{header.label}</th>)
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
Array.from(drivingSchool.students).map((item, index) => <tr key={item.id}>
|
||||||
|
{
|
||||||
|
headersStud.map((header) => <td key={`${header.name}_${item.id}`}>{item[header.name]}</td>)
|
||||||
|
}
|
||||||
|
</tr>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<ModalForm show={isShowHire} onClose={unshowModalFormHire} modalTitle={"Зачисление"} form={formHire}></ModalForm>
|
||||||
|
<ModalForm show={isShowDismiss} onClose={unshowModalFormDismiss} modalTitle={"Отчисление"} form={formDismiss}></ModalForm>
|
||||||
|
<ModalForm show={isShowChooseCategory} onClose={unshowModalFormChooseCategory} modalTitle={"Управление категориями"} form={formCheckBoxesCategory}></ModalForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
export default withAuth(OneDrivingSchool);
|
7
front/src/components/OneDrivingSchool.module.css
Normal file
7
front/src/components/OneDrivingSchool.module.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.prokrutka {
|
||||||
|
background: #fff; /* цвет фона, белый */
|
||||||
|
border: 1px solid #C1C1C1; /* размер и цвет границы блока */
|
||||||
|
overflow: auto;
|
||||||
|
width:200px;
|
||||||
|
height:100px;
|
||||||
|
}
|
75
front/src/components/ReportStudentCategory.jsx
Normal file
75
front/src/components/ReportStudentCategory.jsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import DataService from '../services/DataService';
|
||||||
|
import Student from '../models/Student';
|
||||||
|
import Category from '../models/Category';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import DrivingSchool from '../models/DrivingSchool';
|
||||||
|
import { Link} from "react-router-dom";
|
||||||
|
import withAuth from './withAuth';
|
||||||
|
function ReportStudentCategory(props) {
|
||||||
|
const headersEmp = [
|
||||||
|
{name: 'surname', label: "Фамилия"},
|
||||||
|
{name: 'name', label: "Имя"},
|
||||||
|
{name: 'categoriesString', label: 'Категории'},
|
||||||
|
{name: 'phoneNumber', label: "Номер телефона"},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const url = "/student/category?cat=id"
|
||||||
|
const urlCat = '/category';
|
||||||
|
|
||||||
|
const [itemsCat, setItemsCat] = useState([]);
|
||||||
|
|
||||||
|
const [itemsStud, setItemsStud] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadItemsCtegories();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadItemsCtegories() {
|
||||||
|
DataService.readAll(urlCat, (data) => new Category(data))
|
||||||
|
.then(data => setItemsCat(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadItemsStudents(selectedCategory) {
|
||||||
|
DataService.readAll(url.replace("id", selectedCategory), (data) => new Student(data))
|
||||||
|
.then(data => setItemsStud(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<h1>Отчет</h1>
|
||||||
|
|
||||||
|
<Form.Select onChange={(e) => {loadItemsStudents(e.target.value)}} aria-label="Default select example">
|
||||||
|
<option selected disabled>Выберите категорию</option>
|
||||||
|
{
|
||||||
|
itemsCat.map((e) => <option key={`stud_${e.id}`} value={`${e.id}`}>{`${e.name}`}</option>)
|
||||||
|
}
|
||||||
|
</Form.Select>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<table className={`table table-hover`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
headersStud.map((header) => <th key={header.name}>{header.label}</th>)
|
||||||
|
}
|
||||||
|
<th key='company'>Автошкола</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
itemsStud.map((item) => <tr key={item.id}>
|
||||||
|
{
|
||||||
|
headersStud.map((header) => <td key={`${header.name}_${item.id}`}>{item[header.name]}</td>)
|
||||||
|
}
|
||||||
|
<td key={`ds_${item.id}`}><Link to={`/drivingSchool/${item['drivingSchool'].id}`}>{item['drivingSchool'].name}</Link></td>
|
||||||
|
</tr>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withAuth(ReportStudentCategory);
|
117
front/src/components/Students.jsx
Normal file
117
front/src/components/Students.jsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import Student from "../models/Student";
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import DataService from '../services/DataService';
|
||||||
|
import CatalogSC from "./CatalogSC.jsx";
|
||||||
|
import withAuth from './withAuth';
|
||||||
|
function Students(props) {
|
||||||
|
const headers = [
|
||||||
|
{name: 'surname', label: "Фамилия"},
|
||||||
|
{name: 'name', label: "Имя"},
|
||||||
|
{name: 'phoneNumber', label: "Номер телефона"},
|
||||||
|
{name: 'categoriesString', label: 'Категории'}
|
||||||
|
];
|
||||||
|
const nameCatalog = "Студенты";
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const url = '/student';
|
||||||
|
const requestParams = '?name=nameData&surname=surnameData&phoneNumber=phoneNameData';
|
||||||
|
const [data, setData] = useState(new Student());
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
loadItems();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
function loadItems() {
|
||||||
|
DataService.readAll(url, (data) => new Student(data))
|
||||||
|
.then(data => setItems(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
DataService.create(url +requestParams
|
||||||
|
.replace("nameData", data.name)
|
||||||
|
.replace("surnameData", data.surname)
|
||||||
|
.replace("phoneNameData", data.phoneNumber))
|
||||||
|
.then(() => loadItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(editedId) {
|
||||||
|
DataService.read(url + "/" + editedId, (e) => new Student(e))
|
||||||
|
.then(data => {
|
||||||
|
setData(new Student(data));
|
||||||
|
});
|
||||||
|
setEditing(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
function handleEditIsDone() {
|
||||||
|
|
||||||
|
DataService.update(url + "/" + data.id + requestParams
|
||||||
|
.replace("nameData", data.name)
|
||||||
|
.replace("surnameData", data.surname)
|
||||||
|
.replace("phoneNameData", data.phoneNumber)).then(() => loadItems());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(item) {
|
||||||
|
if (window.confirm('Удалить выбранный элемент?')) {
|
||||||
|
const promises = [];
|
||||||
|
promises.push(DataService.delete(url + "/" + item));
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
loadItems();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// при каждом изменении поля формы происходит вызов этого метода, обновляя данные объекта
|
||||||
|
function handleFormChange(event) {
|
||||||
|
setData({ ...data, [event.target.name]: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// определяет действия формы по нажатию Отправить
|
||||||
|
function submitForm() {
|
||||||
|
if (!isEditing) {
|
||||||
|
// если добавление элемента
|
||||||
|
handleAdd();
|
||||||
|
} else {
|
||||||
|
// если редактирование элемента;
|
||||||
|
handleEditIsDone();
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// вызывается при закрытии модального окна или по нажатию кнопки Добавить и очищает данные объекта
|
||||||
|
function reset() {
|
||||||
|
setData(new Student());
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// для каждого типа сущности своя форма,
|
||||||
|
// которая передается дальше в абстрактный компонент Catalog в качестве props.form
|
||||||
|
const form = <Form onSubmit={submitForm}>
|
||||||
|
<Form.Group className="mb-3" controlId="name">
|
||||||
|
<Form.Label>Фамилия</Form.Label>
|
||||||
|
<Form.Control name="surname" value={data.surname} type="input" placeholder="Enter text" onChange={handleFormChange} required/>
|
||||||
|
<Form.Label>Имя</Form.Label>
|
||||||
|
<Form.Control name="name" value={data.name} type="input" placeholder="Enter text" onChange={handleFormChange} required/>
|
||||||
|
<Form.Label>Номер телефона</Form.Label>
|
||||||
|
<Form.Control name="phoneNumber" value={data.phoneNumber} type="input" placeholder="Enter text" onChange={handleFormChange} required/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
return <div className="container-lg pt-5 min-vh-100">
|
||||||
|
<CatalogSC name={nameCatalog}
|
||||||
|
headers={headers}
|
||||||
|
items={items}
|
||||||
|
onAdd={handleAdd}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onClose={reset}
|
||||||
|
onBtnAdd={reset}
|
||||||
|
form={form}>
|
||||||
|
</CatalogSC>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
export default withAuth(Students);
|
36
front/src/components/commons/Header.jsx
Normal file
36
front/src/components/commons/Header.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import Container from 'react-bootstrap/Container';
|
||||||
|
import Nav from 'react-bootstrap/Nav';
|
||||||
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
import styles from "./Header.module.css";
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function Header(props) {
|
||||||
|
return (
|
||||||
|
<Navbar collapseOnSelect expand="lg" bg="primary" variant="primary">
|
||||||
|
<Container className='lg justify-content-center'>
|
||||||
|
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
|
||||||
|
<Navbar.Collapse id="responsive-navbar-nav">
|
||||||
|
<Nav className="me-auto">
|
||||||
|
{
|
||||||
|
props.links.map(route =>
|
||||||
|
<NavLink key={route.path} className="nav-link" to={route.path}>
|
||||||
|
<div>{route.label}</div>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{props.token && props.token !== 'undefined' ?
|
||||||
|
<NavLink className="nav-link" to="/logout">
|
||||||
|
<div>Выйти</div>
|
||||||
|
</NavLink>
|
||||||
|
:
|
||||||
|
<NavLink className="nav-link" to="/login">
|
||||||
|
<div>Войти</div>
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
|
</Nav>
|
||||||
|
</Navbar.Collapse>
|
||||||
|
</Container>
|
||||||
|
</Navbar>
|
||||||
|
);
|
||||||
|
}
|
23
front/src/components/commons/ItemTable.jsx
Normal file
23
front/src/components/commons/ItemTable.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
export default function ItemTable(props) {
|
||||||
|
function edit() {
|
||||||
|
props.onEdit(props.item.id);
|
||||||
|
}
|
||||||
|
function remove() {
|
||||||
|
props.onDelete(props.item.id);
|
||||||
|
}
|
||||||
|
function chooseDrivingSchool() {
|
||||||
|
props.onChoose(props.item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <tr key={props.item.id}>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <td key={`${header.name}_${props.item.id}`}>{props.item[header.name]}</td>)
|
||||||
|
}
|
||||||
|
{localStorage.getItem("role") !== "ADMIN" ||props.isOnlyView || <td key={`controls_${props.item.id}`}>
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-warning" onClick={chooseDrivingSchool}>Выбрать</Button>}
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>}
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button>}
|
||||||
|
</td>}
|
||||||
|
</tr>
|
||||||
|
}
|
8
front/src/components/commons/ItemTableForGroup.jsx
Normal file
8
front/src/components/commons/ItemTableForGroup.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default function ItemTableForGroup(props) {
|
||||||
|
|
||||||
|
return <tr key={props.item.id}>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <td key={`${header.name}_${props.item.id}`}>{props.item[header.name]}</td>)
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
19
front/src/components/commons/ItemTableSC.jsx
Normal file
19
front/src/components/commons/ItemTableSC.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
export default function ItemTableSC(props) {
|
||||||
|
function edit() {
|
||||||
|
props.onEdit(props.item.id);
|
||||||
|
}
|
||||||
|
function remove() {
|
||||||
|
props.onDelete(props.item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <tr key={props.item.id}>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <td key={`${header.name}_${props.item.id}`}>{props.item[header.name]}</td>)
|
||||||
|
}
|
||||||
|
{localStorage.getItem("role") !== "ADMIN" ||props.isOnlyView || <td key={`controls_${props.item.id}`}>
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-primary" onClick={edit}>Редактировать</Button>}
|
||||||
|
{localStorage.getItem("role") === "ADMIN" && <Button variant="btn btn-outline-danger" onClick={remove}>Удалить</Button>}
|
||||||
|
</td>}
|
||||||
|
</tr>
|
||||||
|
}
|
14
front/src/components/commons/ModalForm.jsx
Normal file
14
front/src/components/commons/ModalForm.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import Modal from 'react-bootstrap/Modal';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import { React} from 'react';
|
||||||
|
export default function ModalForm(props) {
|
||||||
|
return <Modal show={props.show} onHide={props.onClose}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>{props.modalTitle}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
{props.form}
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
}
|
39
front/src/components/commons/Table.jsx
Normal file
39
front/src/components/commons/Table.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import ItemTable from './ItemTable';
|
||||||
|
export default function Table(props) {
|
||||||
|
function edit(itemId) {
|
||||||
|
props.onEdit(itemId)
|
||||||
|
}
|
||||||
|
function remove(itemId) {
|
||||||
|
props.onDelete(itemId);
|
||||||
|
}
|
||||||
|
function chooseDrivingSchool(itemId) {
|
||||||
|
props.onChoose(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div >
|
||||||
|
<table className={`table table-hover`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <th key={header.name}>{header.label}</th>)
|
||||||
|
}
|
||||||
|
{localStorage.getItem("role") !== "ADMIN" || props.isOnlyView || <th key='controls'>Элементы управления</th>}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
props.items.map((item, index) =>
|
||||||
|
<ItemTable
|
||||||
|
key={index}
|
||||||
|
headers={props.headers}
|
||||||
|
item={item}
|
||||||
|
onDelete={remove}
|
||||||
|
onEdit={edit}
|
||||||
|
onChoose={chooseDrivingSchool}
|
||||||
|
isOnlyView={props.isOnlyView}/>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
26
front/src/components/commons/TableForGroup.jsx
Normal file
26
front/src/components/commons/TableForGroup.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import ItemTableForGroup from './ItemTableForGroup';
|
||||||
|
|
||||||
|
export default function TableForGroup(props) {
|
||||||
|
|
||||||
|
return <div >
|
||||||
|
<table className={`table table-hover`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <th key={header.name}>{header.label}</th>)
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
props.items.map((item, index) =>
|
||||||
|
<ItemTableForGroup
|
||||||
|
key={index}
|
||||||
|
headers={props.headers}
|
||||||
|
item={item}
|
||||||
|
isOnlyView={props.isOnlyView}/>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
35
front/src/components/commons/TableSC.jsx
Normal file
35
front/src/components/commons/TableSC.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import ItemTableSC from './ItemTableSC';
|
||||||
|
export default function TableSC(props) {
|
||||||
|
function edit(itemId) {
|
||||||
|
props.onEdit(itemId)
|
||||||
|
}
|
||||||
|
function remove(itemId) {
|
||||||
|
props.onDelete(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div >
|
||||||
|
<table className={`table table-hover`}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
props.headers.map((header) => <th key={header.name}>{header.label}</th>)
|
||||||
|
}
|
||||||
|
{localStorage.getItem("role") !== "ADMIN" || props.isOnlyView || <th key='controls'>Элементы управления</th>}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
props.items.map((item, index) =>
|
||||||
|
<ItemTableSC
|
||||||
|
key={index}
|
||||||
|
headers={props.headers}
|
||||||
|
item={item}
|
||||||
|
onDelete={remove}
|
||||||
|
onEdit={edit}
|
||||||
|
isOnlyView={props.isOnlyView}/>)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
24
front/src/components/withAuth.js
Normal file
24
front/src/components/withAuth.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useNavigate} from "react-router-dom";
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const withAuth = (Component) => {
|
||||||
|
const AuthenticatedComponent = (props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token || token === 'undefined') {
|
||||||
|
navigate('/login');
|
||||||
|
}
|
||||||
|
}, [navigate, token]);
|
||||||
|
|
||||||
|
if (token && token !== 'undefined') {
|
||||||
|
return <Component {...props} />
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return AuthenticatedComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withAuth;
|
8
front/src/index.js
Normal file
8
front/src/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(
|
||||||
|
<App />
|
||||||
|
);
|
6
front/src/models/Category.js
Normal file
6
front/src/models/Category.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default class Category {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data?.id;
|
||||||
|
this.name = data?.name || '';
|
||||||
|
}
|
||||||
|
}
|
13
front/src/models/DrivingSchool.js
Normal file
13
front/src/models/DrivingSchool.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Student from "./Student";
|
||||||
|
|
||||||
|
export default class DrivingSchool {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data?.id;
|
||||||
|
this.name = data?.name || '';
|
||||||
|
this.students = data?.students.map((e) => new Student(e)) || '';
|
||||||
|
this.countStudents = '';
|
||||||
|
if (this.students !== '') {
|
||||||
|
this.countStudents = this.students.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
front/src/models/GroupedStudAndCategoryDto.js
Normal file
7
front/src/models/GroupedStudAndCategoryDto.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default class GroupedStudAndCategoryDto {
|
||||||
|
constructor(data) {
|
||||||
|
this.Id = data?.Id;
|
||||||
|
this.categoryName = data?.categoryName;
|
||||||
|
this.studentsCount = data?.studentsCount;
|
||||||
|
}
|
||||||
|
}
|
25
front/src/models/Student.js
Normal file
25
front/src/models/Student.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Category from "./Category";
|
||||||
|
|
||||||
|
export default class Student {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data?.id;
|
||||||
|
this.surname = data?.surname || '';
|
||||||
|
this.name = data?.name || '';
|
||||||
|
this.phoneNumber = data?.phoneNumber || '';
|
||||||
|
this.categories = data?.categories.map((p) => new Category(p)) || '';
|
||||||
|
this.categoriesString = '';
|
||||||
|
if (this.categories !== '') {
|
||||||
|
this.categories.forEach((p) => {this.categoriesString += p.name + " "});
|
||||||
|
}
|
||||||
|
this.drivingSchool = data?.drivingSchool || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Student.prototype.equals = function (obj){
|
||||||
|
if(typeof obj != typeof this)
|
||||||
|
return false;
|
||||||
|
if (this.id === obj.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
55
front/src/services/DataService.js
Normal file
55
front/src/services/DataService.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
function toJSON(data) {
|
||||||
|
const jsonObj = {};
|
||||||
|
const fields = Object.getOwnPropertyNames(data);
|
||||||
|
for (const field of fields) {
|
||||||
|
if (data[field] === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jsonObj[field] = data[field];
|
||||||
|
}
|
||||||
|
return jsonObj;
|
||||||
|
}
|
||||||
|
const getTokenForHeader = function () {
|
||||||
|
return "Bearer " + localStorage.getItem("token");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DataService {
|
||||||
|
static dataUrlPrefix = 'http://localhost:8080/api';
|
||||||
|
|
||||||
|
static async readAll(url, transformer) {
|
||||||
|
const response = await axios.get(this.dataUrlPrefix + url, {headers: {
|
||||||
|
"Authorization": getTokenForHeader(),
|
||||||
|
}});
|
||||||
|
return response.data.map(item => transformer(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async read(url, transformer) {
|
||||||
|
const response = await axios.get(this.dataUrlPrefix + url, {headers: {
|
||||||
|
"Authorization": getTokenForHeader(),
|
||||||
|
}});
|
||||||
|
return transformer(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(url, data) {
|
||||||
|
const response = await axios.post(this.dataUrlPrefix + url, {headers: {
|
||||||
|
"Authorization": getTokenForHeader(),
|
||||||
|
}});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async update(url, data) {
|
||||||
|
const response = await axios.put(this.dataUrlPrefix + url, {headers: {
|
||||||
|
"Authorization": getTokenForHeader(),
|
||||||
|
}});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async delete(url) {
|
||||||
|
const response = await axios.delete(this.dataUrlPrefix + url, {headers: {
|
||||||
|
"Authorization": getTokenForHeader(),
|
||||||
|
}});
|
||||||
|
return response.data.id;
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class CbappApplication {
|
public class CbappApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(CbappApplication.class, args);
|
SpringApplication.run(CbappApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
21
src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java
Normal file
21
src/main/java/ru/ulstu/is/cbapp/WebConfiguration.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package ru.ulstu.is.cbapp;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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 {
|
||||||
|
public static final String REST_API = "/api";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addViewControllers(ViewControllerRegistry registry) {
|
||||||
|
WebMvcConfigurer.super.addViewControllers(registry);
|
||||||
|
registry.addViewController("login");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**").allowedMethods("*");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.ulstu.is.cbapp.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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package ru.ulstu.is.cbapp.configuration;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.configuration.jwt.JwtFilter;
|
||||||
|
import ru.ulstu.is.cbapp.user.controller.UserController;
|
||||||
|
import ru.ulstu.is.cbapp.user.controller.UserSignupMvcController;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserRole;
|
||||||
|
import ru.ulstu.is.cbapp.user.service.UserService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||||
|
private static final String LOGIN_URL = "/login";
|
||||||
|
public static final String SPA_URL_MASK = "/{path:[^\\.]*}";
|
||||||
|
private UserService userService;
|
||||||
|
private JwtFilter jwtFilter;
|
||||||
|
|
||||||
|
public SecurityConfiguration(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.jwtFilter = new JwtFilter(userService);
|
||||||
|
createAdminOnStartup();
|
||||||
|
createTestUsersOnStartup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAdminOnStartup() {
|
||||||
|
final String admin = "admin";
|
||||||
|
if (userService.findByLogin(admin) == null) {
|
||||||
|
log.info("Admin user successfully created");
|
||||||
|
userService.createUser(admin, admin, admin, UserRole.ADMIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTestUsersOnStartup() {
|
||||||
|
final String[] userNames = {"user1", "user2", "user3"};
|
||||||
|
for (String user : userNames) {
|
||||||
|
if (userService.findByLogin(user) == null) {
|
||||||
|
log.info("User %s successfully created".formatted(user));
|
||||||
|
userService.createUser(user, user, user, UserRole.USER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.cors()
|
||||||
|
.and()
|
||||||
|
.csrf().disable()
|
||||||
|
.authorizeHttpRequests((a) -> a
|
||||||
|
.requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
|
||||||
|
.requestMatchers(HttpMethod.PUT, "/api/**").hasRole("ADMIN")
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN")
|
||||||
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
.requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll())
|
||||||
|
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.anonymous().and().authorizeHttpRequests((a) ->
|
||||||
|
a.requestMatchers(LOGIN_URL, UserSignupMvcController.SIGNUP_URL, "/h2-console/**")
|
||||||
|
.permitAll().requestMatchers("/users").hasRole("ADMIN").anyRequest().authenticated())
|
||||||
|
.formLogin()
|
||||||
|
.loginPage(LOGIN_URL).permitAll()
|
||||||
|
.and()
|
||||||
|
.logout().permitAll()
|
||||||
|
.logoutSuccessUrl("/login")
|
||||||
|
.and()
|
||||||
|
.userDetailsService(userService);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
return (web) -> web.ignoring().requestMatchers("/css/**", "/js/**", "/templates/**", "/webjars/**", "/styles/**"); }
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.ulstu.is.cbapp.configuration.jwt;
|
||||||
|
|
||||||
|
public class JwtException extends RuntimeException {
|
||||||
|
public JwtException(Throwable throwable) {
|
||||||
|
super(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package ru.ulstu.is.cbapp.configuration.jwt;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.user.service.UserService;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
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.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.GenericFilterBean;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class JwtFilter extends AbstractPreAuthenticatedProcessingFilter {
|
||||||
|
private static final String AUTHORIZATION = "Authorization";
|
||||||
|
public static final String TOKEN_BEGIN_STR = "Bearer ";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public JwtFilter(UserService 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
if (httpRequest.getRequestURI().startsWith("/api/")) {
|
||||||
|
// Для URL, начинающихся с /api/, выполняем проверку наличия токена
|
||||||
|
super.doFilter(request, response, chain);
|
||||||
|
} else {
|
||||||
|
// Для остальных URL выполняем авторизацию
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||||
|
String token = getTokenFromRequest(request);
|
||||||
|
// Возвращаем токен как принципала
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||||
|
return new WebAuthenticationDetailsSource().buildDetails(request);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.is.cbapp.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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package ru.ulstu.is.cbapp.configuration.jwt;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.JwtBuilder;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtsProvider {
|
||||||
|
|
||||||
|
@Value("jwt.secret")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
public String generateToken(String login, String role) {
|
||||||
|
Date date = Date.from(LocalDate.now().plusDays(15).atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
|
||||||
|
JwtBuilder builder = Jwts.builder()
|
||||||
|
.setSubject(login)
|
||||||
|
.setExpiration(date)
|
||||||
|
.signWith(SignatureAlgorithm.HS512, secret);
|
||||||
|
Claims claims = Jwts.claims();
|
||||||
|
claims.put("role", role);
|
||||||
|
builder.addClaims(claims);
|
||||||
|
return builder.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogin(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(secret)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody()
|
||||||
|
.getSubject();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package ru.ulstu.is.cbapp.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import ru.ulstu.is.cbapp.WebConfiguration;
|
||||||
|
import ru.ulstu.is.cbapp.dto.CategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(WebConfiguration.REST_API + "/category")
|
||||||
|
public class CategoryController {
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public CategoryController(CategoryService categoryService) {
|
||||||
|
this.categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public CategoryDto getDrivingSchool(@PathVariable long id) {
|
||||||
|
return new CategoryDto(categoryService.findCategory(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<CategoryDto> getAllDrivingSchools() {
|
||||||
|
return categoryService.findAllCategories().stream()
|
||||||
|
.map(CategoryDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public CategoryDto create(@RequestBody @Valid CategoryDto categoryDto) {
|
||||||
|
return new CategoryDto(categoryService.addCategory(categoryDto.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public CategoryDto update(@PathVariable Long id,
|
||||||
|
@RequestBody @Valid CategoryDto categoryDto) {
|
||||||
|
return new CategoryDto(categoryService.updateCategory(id, categoryDto.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public CategoryDto delete(@PathVariable Long id) {
|
||||||
|
return new CategoryDto(categoryService.deleteCategory(id));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.is.cbapp.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import ru.ulstu.is.cbapp.WebConfiguration;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryStudentService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(WebConfiguration.REST_API+"/categoryStudent")
|
||||||
|
public class CategoryStudentController {
|
||||||
|
|
||||||
|
private CategoryStudentService categoryStudentService;
|
||||||
|
|
||||||
|
public CategoryStudentController(CategoryStudentService categoryStudentService) {
|
||||||
|
this.categoryStudentService = categoryStudentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/groupbycategory")
|
||||||
|
public List<GroupedStudAndCategoryDto> getGroupedStudAndCategory(){
|
||||||
|
return categoryStudentService.getGroupedStudAndCategory();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package ru.ulstu.is.cbapp.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import ru.ulstu.is.cbapp.WebConfiguration;
|
||||||
|
import ru.ulstu.is.cbapp.dto.DrivingSchoolDto;
|
||||||
|
import ru.ulstu.is.cbapp.dto.StudentDto;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import ru.ulstu.is.cbapp.service.DrivingSchoolService;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(WebConfiguration.REST_API + "/drivingSchool")
|
||||||
|
public class DrivingSchoolController {
|
||||||
|
private final DrivingSchoolService drivingSchoolService;
|
||||||
|
private final StudentService studentService;
|
||||||
|
|
||||||
|
public DrivingSchoolController(DrivingSchoolService drivingSchoolService, StudentService studentService) {
|
||||||
|
this.drivingSchoolService = drivingSchoolService;
|
||||||
|
this.studentService = studentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public DrivingSchoolDto getDrivingSchool(@PathVariable long id) {
|
||||||
|
return new DrivingSchoolDto(drivingSchoolService.findDrivingSchool(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/students")
|
||||||
|
public List<StudentDto> getDrivingSchoolStudents(@PathVariable long id) {
|
||||||
|
return drivingSchoolService.findDrivingSchool(id).getStudents().stream().map(StudentDto::new).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<DrivingSchoolDto> getAllDrivingSchools() {
|
||||||
|
return drivingSchoolService.findAllDrivingSchools().stream()
|
||||||
|
.map(DrivingSchoolDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public DrivingSchoolDto create(@RequestBody @Valid DrivingSchoolDto drivingSchoolDto) {
|
||||||
|
return new DrivingSchoolDto(drivingSchoolService.addDrivingSchool(drivingSchoolDto.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public DrivingSchoolDto update(@PathVariable Long id,
|
||||||
|
@RequestBody @Valid DrivingSchoolDto drivingSchoolDto) {
|
||||||
|
return new DrivingSchoolDto(drivingSchoolService.updateDrivingSchool(id, drivingSchoolDto.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public DrivingSchoolDto delete(@PathVariable Long id) {
|
||||||
|
return new DrivingSchoolDto(drivingSchoolService.deleteDrivingSchool(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/hire")
|
||||||
|
public StudentDto hire(@PathVariable Long id, @RequestParam Long studentId) {
|
||||||
|
Student e = studentService.findStudent(studentId);
|
||||||
|
return new StudentDto(drivingSchoolService.addNewStudent(id, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/dismiss")
|
||||||
|
public void dismiss(@PathVariable Long id, @RequestParam Long studentId) {
|
||||||
|
Student e = studentService.findStudent(studentId);
|
||||||
|
drivingSchoolService.deleteStudent(id, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package ru.ulstu.is.cbapp.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import ru.ulstu.is.cbapp.WebConfiguration;
|
||||||
|
import ru.ulstu.is.cbapp.dto.StudentDto;
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(WebConfiguration.REST_API + "/student")
|
||||||
|
public class StudentController {
|
||||||
|
private final StudentService studentService;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public StudentController(StudentService studentService, CategoryService categoryService) {
|
||||||
|
this.studentService = studentService;
|
||||||
|
this.categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public StudentDto getStudent(@PathVariable long id) {
|
||||||
|
return new StudentDto(studentService.findStudent(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<StudentDto> getAllStudents() {
|
||||||
|
return studentService.findAllStudents().stream()
|
||||||
|
.map(StudentDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public StudentDto createStudent(@RequestBody @Valid StudentDto studentDto) {
|
||||||
|
return new StudentDto(studentService.addStudent(
|
||||||
|
studentDto.getSurname(),
|
||||||
|
studentDto.getName(),
|
||||||
|
studentDto.getPhoneNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public StudentDto updateStudent(@PathVariable Long id,
|
||||||
|
@RequestBody @Valid StudentDto studentDto) {
|
||||||
|
return new StudentDto(studentService.updateStudent(id, studentDto.getSurname(), studentDto.getName(), studentDto.getPhoneNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public StudentDto deleteStudent(@PathVariable Long id) {
|
||||||
|
return new StudentDto(studentService.deleteStudent(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/addCat")
|
||||||
|
public StudentDto addCategory(@PathVariable Long id,
|
||||||
|
@RequestParam("category") Long category) {
|
||||||
|
Category p = categoryService.findCategory(category);
|
||||||
|
if (p == null)
|
||||||
|
return null;
|
||||||
|
return new StudentDto(studentService.addCategory(id, p));
|
||||||
|
}
|
||||||
|
@PutMapping("/{id}/delCat")
|
||||||
|
public StudentDto delCategory(@PathVariable Long id,
|
||||||
|
@RequestParam("category") Long category) {
|
||||||
|
Category p = categoryService.findCategory(category);
|
||||||
|
if (p == null)
|
||||||
|
return null;
|
||||||
|
return new StudentDto(studentService.deleteCategory(id, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/free")
|
||||||
|
public List<StudentDto> getAllFreeStudents() {
|
||||||
|
return studentService.findAllFreeStudents().stream()
|
||||||
|
.map(StudentDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/category")
|
||||||
|
public List<StudentDto> getStudentsByCategory(@RequestParam Long cat) {
|
||||||
|
Category p = categoryService.findCategory(cat);
|
||||||
|
if (p == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return studentService.getStudentsByCategory(p).stream()
|
||||||
|
.map(StudentDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
13
src/main/java/ru/ulstu/is/cbapp/dao/CategoryRepository.java
Normal file
13
src/main/java/ru/ulstu/is/cbapp/dao/CategoryRepository.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dao;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface CategoryRepository extends JpaRepository<Category, Long> {
|
||||||
|
Optional<Category> findById(Long id);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dao;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface DrivingSchoolRepository extends JpaRepository<DrivingSchool, Long> {
|
||||||
|
Optional<DrivingSchool> findById(Long id);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dao;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.models.StudentCategories;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface StudentCategoriesRepository extends JpaRepository<StudentCategories, Long> {
|
||||||
|
|
||||||
|
Optional<StudentCategories> findById(Long id);
|
||||||
|
|
||||||
|
@Query("SELECT new ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto(cs.category.name,COUNT(cs.student.id))"+
|
||||||
|
"FROM StudentCategories cs "+
|
||||||
|
"group by cs.category.name")
|
||||||
|
List<GroupedStudAndCategoryDto> getGroupedStudAndCategory();
|
||||||
|
}
|
13
src/main/java/ru/ulstu/is/cbapp/dao/StudentRepository.java
Normal file
13
src/main/java/ru/ulstu/is/cbapp/dao/StudentRepository.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dao;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface StudentRepository extends JpaRepository<Student, Long> {
|
||||||
|
Optional<Student> findById(Long id);
|
||||||
|
List<Student> findByCategories_Id(Long p_id);
|
||||||
|
List<Student> findByDrivingSchoolIsNull();
|
||||||
|
}
|
28
src/main/java/ru/ulstu/is/cbapp/dto/CategoryDto.java
Normal file
28
src/main/java/ru/ulstu/is/cbapp/dto/CategoryDto.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dto;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
|
||||||
|
public class CategoryDto {
|
||||||
|
private Long Id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public CategoryDto(Category category) {
|
||||||
|
Id = category.getId();
|
||||||
|
this.name = category.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CategoryDto() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/ru/ulstu/is/cbapp/dto/DrivingSchoolDto.java
Normal file
35
src/main/java/ru/ulstu/is/cbapp/dto/DrivingSchoolDto.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DrivingSchoolDto {
|
||||||
|
private Long Id;
|
||||||
|
private String name;
|
||||||
|
private List<StudentDto> students;
|
||||||
|
|
||||||
|
public DrivingSchoolDto(DrivingSchool drivingSchool) {
|
||||||
|
Id = drivingSchool.getId();
|
||||||
|
this.name = drivingSchool.getName();
|
||||||
|
this.students = drivingSchool.getStudents().stream().map(StudentDto::new).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrivingSchoolDto() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<StudentDto> getStudents() {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dto;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
|
||||||
|
public class DrivingSchoolWithoutStudDto {
|
||||||
|
private final Long Id;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public DrivingSchoolWithoutStudDto(DrivingSchool drivingSchool) {
|
||||||
|
this.Id = drivingSchool.getId();
|
||||||
|
this.name = drivingSchool.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dto;
|
||||||
|
|
||||||
|
public class GroupedStudAndCategoryDto {
|
||||||
|
private String categoryName;
|
||||||
|
private Long StudentsCount;
|
||||||
|
|
||||||
|
public GroupedStudAndCategoryDto(String categoryName, Long StudentsCount){
|
||||||
|
this.categoryName=categoryName;
|
||||||
|
this.StudentsCount=StudentsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryId(String categoryName) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getStudentsCount() {
|
||||||
|
return StudentsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStudentsCount(Long studentsCount) {
|
||||||
|
StudentsCount = studentsCount;
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/ru/ulstu/is/cbapp/dto/StudentDto.java
Normal file
63
src/main/java/ru/ulstu/is/cbapp/dto/StudentDto.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package ru.ulstu.is.cbapp.dto;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StudentDto {
|
||||||
|
private Long Id;
|
||||||
|
private String name;
|
||||||
|
private String phoneNumber;
|
||||||
|
private String surname;
|
||||||
|
private List<CategoryDto> categories;
|
||||||
|
|
||||||
|
private DrivingSchoolWithoutStudDto drivingSchool;
|
||||||
|
|
||||||
|
public StudentDto(Student student) {
|
||||||
|
Id = student.getId();
|
||||||
|
this.name = student.getName();
|
||||||
|
this.phoneNumber = student.getPhoneNumber();
|
||||||
|
this.surname = student.getSurname();
|
||||||
|
this.categories = student.getCategories().stream().map(CategoryDto::new).toList();
|
||||||
|
this.drivingSchool = student.getDrivingSchool() != null ? new DrivingSchoolWithoutStudDto(student.getDrivingSchool()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StudentDto() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneNumber(String phoneNumber) {
|
||||||
|
this.phoneNumber = phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurname(String surname) {
|
||||||
|
this.surname = surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhoneNumber() {
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSurname() {
|
||||||
|
return surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CategoryDto> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrivingSchoolWithoutStudDto getDrivingSchool() {
|
||||||
|
return drivingSchool;
|
||||||
|
}
|
||||||
|
}
|
71
src/main/java/ru/ulstu/is/cbapp/models/Category.java
Normal file
71
src/main/java/ru/ulstu/is/cbapp/models/Category.java
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package ru.ulstu.is.cbapp.models;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Category {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private Long Id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
//mappedBy - атрибут, указывающий, что классом-владельцем отношений является другой класс
|
||||||
|
@ManyToMany(mappedBy = "categories", cascade = {CascadeType.REMOVE}, fetch = FetchType.EAGER)
|
||||||
|
private Set<Student> students = new HashSet<>();
|
||||||
|
|
||||||
|
public Category() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Category(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Student> getStudents() {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStudents(Set<Student> students) {
|
||||||
|
this.students = students;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNewStudent(Student e) {
|
||||||
|
students.add(e);
|
||||||
|
if (!e.getCategories().contains(this)) {
|
||||||
|
e.addNewCategory(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void deleteStudent(Student e) {
|
||||||
|
students.remove(e);
|
||||||
|
if (e.getCategories().contains(this)) {
|
||||||
|
e.removeCategory(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Category student = (Category) o;
|
||||||
|
return Objects.equals(Id, student.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(Id);
|
||||||
|
}
|
||||||
|
}
|
79
src/main/java/ru/ulstu/is/cbapp/models/DrivingSchool.java
Normal file
79
src/main/java/ru/ulstu/is/cbapp/models/DrivingSchool.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package ru.ulstu.is.cbapp.models;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class DrivingSchool {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private Long Id;
|
||||||
|
@Column(unique = true)
|
||||||
|
private String name;
|
||||||
|
@OneToMany(mappedBy = "drivingSchool", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
|
||||||
|
private Set<Student> students = new HashSet<>();
|
||||||
|
|
||||||
|
public DrivingSchool(String name, Set<Student> students) {
|
||||||
|
this.name = name;
|
||||||
|
this.students = students;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrivingSchool(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrivingSchool() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNewStudent(Student student) {
|
||||||
|
students.add(student);
|
||||||
|
student.setDrivingSchool(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteStudent(Student student) {
|
||||||
|
students.remove(student);
|
||||||
|
student.deleteDrivingSchool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Student> getStudents() {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStudents(Set<Student> students) {
|
||||||
|
this.students = students;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DrivingSchool drivingSchool = (DrivingSchool) o;
|
||||||
|
return Objects.equals(Id, drivingSchool.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DrivingSchool{" +
|
||||||
|
"Id=" + Id +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", students=" + students +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
113
src/main/java/ru/ulstu/is/cbapp/models/Student.java
Normal file
113
src/main/java/ru/ulstu/is/cbapp/models/Student.java
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package ru.ulstu.is.cbapp.models;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Student {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String phoneNumber;
|
||||||
|
private String name;
|
||||||
|
private String surname;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
private DrivingSchool drivingSchool;
|
||||||
|
|
||||||
|
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
//@JoinTable(name="CATEGORY_STUDENT",
|
||||||
|
//joinColumns = {@JoinColumn(name="student_id")},
|
||||||
|
//inverseJoinColumns = {@JoinColumn(name="category_id")})
|
||||||
|
private Set<Category> categories = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
public Student(String surname, String name, String phoneNumber) {
|
||||||
|
this.phoneNumber = phoneNumber;
|
||||||
|
this.name = name;
|
||||||
|
this.surname = surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Student() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhoneNumber() {
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneNumber(String phoneNumber) {
|
||||||
|
this.phoneNumber = phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSurname() {
|
||||||
|
return surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurname(String surname) {
|
||||||
|
this.surname = surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Category> getCategories() {
|
||||||
|
return categories.stream().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrivingSchool getDrivingSchool() {
|
||||||
|
return drivingSchool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrivingSchool(DrivingSchool drivingSchool) {
|
||||||
|
this.drivingSchool = drivingSchool;
|
||||||
|
if (!drivingSchool.getStudents().contains(this)) {
|
||||||
|
drivingSchool.addNewStudent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteDrivingSchool() {
|
||||||
|
if (drivingSchool.getStudents().contains(this)) {
|
||||||
|
drivingSchool.deleteStudent(this);
|
||||||
|
}
|
||||||
|
this.drivingSchool = null;
|
||||||
|
this.categories.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNewCategory(Category p) {
|
||||||
|
categories.add(p);
|
||||||
|
if (!p.getStudents().contains(this)) {
|
||||||
|
p.addNewStudent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCategory(Category p) {
|
||||||
|
categories.remove(p);
|
||||||
|
if (p.getStudents().contains(this)) {
|
||||||
|
p.deleteStudent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Student e = (Student) o;
|
||||||
|
return Objects.equals(id, e.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package ru.ulstu.is.cbapp.models;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name="student_categories")
|
||||||
|
@IdClass(StudentCategoriesId.class)
|
||||||
|
public class StudentCategories {
|
||||||
|
|
||||||
|
public Student getStudent() {
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStudent(Student student) {
|
||||||
|
this.student = student;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Category getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(Category category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name="students_id")
|
||||||
|
private Student student;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name="categories_id")
|
||||||
|
private Category category;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package ru.ulstu.is.cbapp.models;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class StudentCategoriesId implements Serializable {
|
||||||
|
private Long student;
|
||||||
|
private Long category;
|
||||||
|
|
||||||
|
public Long getStudent() {
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStudent(Long student) {
|
||||||
|
this.student = student;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(Long category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package ru.ulstu.is.cbapp.mvc;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import ru.ulstu.is.cbapp.dto.CategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/category")
|
||||||
|
public class CategoryMvcController {
|
||||||
|
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public CategoryMvcController(CategoryService categoryService) {
|
||||||
|
this.categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String getCategories(Model model) {
|
||||||
|
model.addAttribute("categories",
|
||||||
|
categoryService.findAllCategories().stream()
|
||||||
|
.map(CategoryDto::new)
|
||||||
|
.toList());
|
||||||
|
return "category";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/edit/", "/edit/{id}"})
|
||||||
|
public String editCategory(@PathVariable(required = false) Long id,
|
||||||
|
Model model) {
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
model.addAttribute("categoryDto", new CategoryDto());
|
||||||
|
} else {
|
||||||
|
model.addAttribute("categoryId", id);
|
||||||
|
model.addAttribute("categoryDto", new CategoryDto(categoryService.findCategory(id)));
|
||||||
|
}
|
||||||
|
return "category-edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = {"/", "/{id}"})
|
||||||
|
public String saveCategory(@PathVariable(required = false) Long id,
|
||||||
|
@ModelAttribute @Valid CategoryDto categoryDto,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||||
|
return "category-edit";
|
||||||
|
}
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
categoryService.addCategory(categoryDto.getName());
|
||||||
|
} else {
|
||||||
|
categoryService.updateCategory(id, categoryDto.getName());
|
||||||
|
}
|
||||||
|
return "redirect:/category";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete/{id}")
|
||||||
|
public String deleteStudent(@PathVariable Long id) {
|
||||||
|
categoryService.deleteCategory(id);
|
||||||
|
return "redirect:/category";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.is.cbapp.mvc;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryStudentService;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/categoryStudent")
|
||||||
|
public class CategoryStudentMvcController {
|
||||||
|
private final CategoryStudentService categoryStudentService;
|
||||||
|
|
||||||
|
public CategoryStudentMvcController(CategoryStudentService categoryStudentService) {
|
||||||
|
this.categoryStudentService = categoryStudentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/groupbycategory")
|
||||||
|
public String getGroupedStudAndCategory(Model model) {
|
||||||
|
List<GroupedStudAndCategoryDto> GroupedStudAndCategoryDtos = categoryStudentService.getGroupedStudAndCategory();
|
||||||
|
model.addAttribute("GroupedStudAndCategoryDtos", GroupedStudAndCategoryDtos);
|
||||||
|
return "groupbycategory";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package ru.ulstu.is.cbapp.mvc;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import ru.ulstu.is.cbapp.dto.CategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.dto.DrivingSchoolDto;
|
||||||
|
import ru.ulstu.is.cbapp.dto.StudentDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import ru.ulstu.is.cbapp.service.DrivingSchoolService;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/drivingSchool")
|
||||||
|
public class DrivingSchoolMvcController {
|
||||||
|
private final DrivingSchoolService drivingSchoolService;
|
||||||
|
private final StudentService studentService;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public DrivingSchoolMvcController(DrivingSchoolService drivingSchoolService, StudentService studentService, CategoryService categoryService) {
|
||||||
|
this.drivingSchoolService = drivingSchoolService;
|
||||||
|
this.studentService = studentService;
|
||||||
|
this.categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String getDrivingSchools(Model model) {
|
||||||
|
model.addAttribute("drivingSchools",
|
||||||
|
drivingSchoolService.findAllDrivingSchools().stream()
|
||||||
|
.map(DrivingSchoolDto::new)
|
||||||
|
.toList());
|
||||||
|
return "drivingSchool";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/one/{id}")
|
||||||
|
public String getDrivingSchool(@PathVariable(required = false) Long id, Model model) {
|
||||||
|
model.addAttribute("drivingSchool",
|
||||||
|
new DrivingSchoolDto(drivingSchoolService.findDrivingSchool(id)));
|
||||||
|
model.addAttribute("freeStudents", studentService.findAllFreeStudents().stream().map(StudentDto::new).toList());
|
||||||
|
model.addAttribute("categories", categoryService.findAllCategories().stream().map(CategoryDto::new).toList());
|
||||||
|
return "drivingSchool-one";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/redirect")
|
||||||
|
public String redirectToDrivingSchoolPage(@RequestParam("drivingSchoolId") Long drivingSchoolId) {
|
||||||
|
return "redirect:/drivingSchool/one/" + drivingSchoolId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/edit", "/edit/{id}"})
|
||||||
|
public String editDrivingSchool(@PathVariable(required = false) Long id,
|
||||||
|
Model model) {
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
model.addAttribute("drivingSchoolDto", new DrivingSchoolDto());
|
||||||
|
} else {
|
||||||
|
model.addAttribute("drivingSchoolId", id);
|
||||||
|
model.addAttribute("drivingSchoolDto", new DrivingSchoolDto(drivingSchoolService.findDrivingSchool(id)));
|
||||||
|
}
|
||||||
|
return "drivingSchool-edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = {"/", "/{id}"})
|
||||||
|
public String saveDrivingSchool(@PathVariable(required = false) Long id,
|
||||||
|
@ModelAttribute @Valid DrivingSchoolDto drivingSchoolDto,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||||
|
return "drivingSchool-edit";
|
||||||
|
}
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
drivingSchoolService.addDrivingSchool(drivingSchoolDto.getName());
|
||||||
|
} else {
|
||||||
|
drivingSchoolService.updateDrivingSchool(id, drivingSchoolDto.getName());
|
||||||
|
}
|
||||||
|
return "redirect:/drivingSchool";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete/{id}")
|
||||||
|
public String deleteDrivingSchool(@PathVariable Long id) {
|
||||||
|
drivingSchoolService.deleteDrivingSchool(id);
|
||||||
|
return "redirect:/drivingSchool";
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/ru/ulstu/is/cbapp/mvc/HomeController.java
Normal file
16
src/main/java/ru/ulstu/is/cbapp/mvc/HomeController.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package ru.ulstu.is.cbapp.mvc;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class HomeController {
|
||||||
|
|
||||||
|
@ModelAttribute("requestURI")
|
||||||
|
public String requestURI(final HttpServletRequest request) {
|
||||||
|
return request.getRequestURI();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package ru.ulstu.is.cbapp.mvc;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import ru.ulstu.is.cbapp.dto.CategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.dto.StudentDto;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/student")
|
||||||
|
public class StudentMvcController {
|
||||||
|
private final StudentService studentService;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public StudentMvcController(StudentService studentService, CategoryService categoryService) {
|
||||||
|
this.studentService = studentService;
|
||||||
|
this.categoryService = categoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String getStudents(Model model) {
|
||||||
|
model.addAttribute("students",
|
||||||
|
studentService.findAllStudents().stream()
|
||||||
|
.map(StudentDto::new)
|
||||||
|
.toList());
|
||||||
|
return "student";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = {"/edit/", "/edit/{id}"})
|
||||||
|
public String editDrivingSchool(@PathVariable(required = false) Long id,
|
||||||
|
Model model) {
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
model.addAttribute("studentDto", new StudentDto());
|
||||||
|
} else {
|
||||||
|
model.addAttribute("studentId", id);
|
||||||
|
model.addAttribute("studentDto", new StudentDto(studentService.findStudent(id)));
|
||||||
|
}
|
||||||
|
return "student-edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = {"/", "/{id}"})
|
||||||
|
public String saveDrivingSchool(@PathVariable(required = false) Long id,
|
||||||
|
@ModelAttribute @Valid StudentDto studentDto,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||||
|
return "student-edit";
|
||||||
|
}
|
||||||
|
if (id == null || id <= 0) {
|
||||||
|
studentService.addStudent(studentDto.getSurname(), studentDto.getName(), studentDto.getPhoneNumber());
|
||||||
|
} else {
|
||||||
|
studentService.updateStudent(id, studentDto.getSurname(), studentDto.getName(), studentDto.getPhoneNumber());
|
||||||
|
}
|
||||||
|
return "redirect:/student";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/delete/{id}")
|
||||||
|
public String deleteStudent(@PathVariable Long id) {
|
||||||
|
studentService.deleteStudent(id);
|
||||||
|
return "redirect:/student";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
src/main/java/ru/ulstu/is/cbapp/service/CategoryService.java
Normal file
69
src/main/java/ru/ulstu/is/cbapp/service/CategoryService.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package ru.ulstu.is.cbapp.service;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.dao.CategoryRepository;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CategoryService {
|
||||||
|
|
||||||
|
private CategoryRepository categoryRepository;
|
||||||
|
|
||||||
|
public CategoryService(CategoryRepository categoryRepository) {
|
||||||
|
this.categoryRepository = categoryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Category addCategory(String name) {
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
throw new IllegalArgumentException("Student's data is null or empty");
|
||||||
|
}
|
||||||
|
final Category category = new Category(name);
|
||||||
|
categoryRepository.save(category);
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Category findCategory(Long id) {
|
||||||
|
final Category category = categoryRepository.findById(id).orElse(null);
|
||||||
|
if (category == null) {
|
||||||
|
throw new EntityNotFoundException(String.format("Category with id [%s] is not found", id));
|
||||||
|
}
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<Category> findAllCategories() {
|
||||||
|
return categoryRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Category updateCategory(Long id, String name) {
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
throw new IllegalArgumentException("Student's data is null or empty");
|
||||||
|
}
|
||||||
|
final Category currentCategory = findCategory(id);
|
||||||
|
currentCategory.setName(name);
|
||||||
|
|
||||||
|
return categoryRepository.save(currentCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Category deleteCategory(Long id) {
|
||||||
|
final Category p = findCategory(id);
|
||||||
|
categoryRepository.delete(p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteAllCategories() {
|
||||||
|
categoryRepository.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package ru.ulstu.is.cbapp.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import ru.ulstu.is.cbapp.dao.StudentCategoriesRepository;
|
||||||
|
import ru.ulstu.is.cbapp.dto.GroupedStudAndCategoryDto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CategoryStudentService {
|
||||||
|
|
||||||
|
private StudentCategoriesRepository studentCategoriesRepository;
|
||||||
|
|
||||||
|
public CategoryStudentService(StudentCategoriesRepository studentCategoriesRepository) {
|
||||||
|
this.studentCategoriesRepository = studentCategoriesRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<GroupedStudAndCategoryDto> getGroupedStudAndCategory() {
|
||||||
|
return studentCategoriesRepository.getGroupedStudAndCategory();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package ru.ulstu.is.cbapp.service;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.dao.DrivingSchoolRepository;
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DrivingSchoolService {
|
||||||
|
|
||||||
|
private DrivingSchoolRepository drivingSchoolRepository;
|
||||||
|
private StudentService studentService;
|
||||||
|
|
||||||
|
public DrivingSchoolService(DrivingSchoolRepository drivingSchoolRepository, StudentService studentService) {
|
||||||
|
this.drivingSchoolRepository = drivingSchoolRepository;
|
||||||
|
this.studentService = studentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DrivingSchool addDrivingSchool(String name) {
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
throw new IllegalArgumentException("Student name is null or empty");
|
||||||
|
}
|
||||||
|
final DrivingSchool drivingSchool = new DrivingSchool(name);
|
||||||
|
drivingSchoolRepository.save(drivingSchool);
|
||||||
|
return drivingSchool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public DrivingSchool findDrivingSchool(Long id) {
|
||||||
|
final DrivingSchool drivingSchool = drivingSchoolRepository.findById(id).orElse(null);
|
||||||
|
if (drivingSchool == null) {
|
||||||
|
throw new EntityNotFoundException(String.format("DrivingSchool with id [%s] is not found", id));
|
||||||
|
}
|
||||||
|
return drivingSchool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<DrivingSchool> findAllDrivingSchools() {
|
||||||
|
return drivingSchoolRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DrivingSchool updateDrivingSchool(Long id, String name) {
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
throw new IllegalArgumentException("DrivingSchool name is null or empty");
|
||||||
|
}
|
||||||
|
final DrivingSchool currentDrivingSchool = findDrivingSchool(id);
|
||||||
|
currentDrivingSchool.setName(name);
|
||||||
|
|
||||||
|
return drivingSchoolRepository.save(currentDrivingSchool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DrivingSchool deleteDrivingSchool(Long id) {
|
||||||
|
final DrivingSchool currentDrivingSchool = findDrivingSchool(id);
|
||||||
|
drivingSchoolRepository.delete(currentDrivingSchool);
|
||||||
|
return currentDrivingSchool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteAllDrivingSchools() {
|
||||||
|
drivingSchoolRepository.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student addNewStudent(Long id, Student student) {
|
||||||
|
DrivingSchool currentDrivingSchool = findDrivingSchool(id);
|
||||||
|
currentDrivingSchool.addNewStudent(student);
|
||||||
|
drivingSchoolRepository.save(currentDrivingSchool);
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteStudent(Long id, Student student) {
|
||||||
|
DrivingSchool currentDrivingSchool = findDrivingSchool(id);
|
||||||
|
currentDrivingSchool.deleteStudent(student);
|
||||||
|
}
|
||||||
|
}
|
111
src/main/java/ru/ulstu/is/cbapp/service/StudentService.java
Normal file
111
src/main/java/ru/ulstu/is/cbapp/service/StudentService.java
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package ru.ulstu.is.cbapp.service;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.dao.StudentRepository;
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StudentService {
|
||||||
|
|
||||||
|
private StudentRepository studentRepository;
|
||||||
|
|
||||||
|
public StudentService(StudentRepository studentRepository) {
|
||||||
|
this.studentRepository = studentRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student addStudent(String surname, String name, String phoneNumber) {
|
||||||
|
if (!StringUtils.hasText(name) ||!StringUtils.hasText(surname) || !StringUtils.hasText(phoneNumber)) {
|
||||||
|
throw new IllegalArgumentException("Student's data is null or empty");
|
||||||
|
}
|
||||||
|
final Student student = new Student(surname, name, phoneNumber);
|
||||||
|
studentRepository.save(student);
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student findStudent(Long id) {
|
||||||
|
final Student student = studentRepository.findById(id).orElse(null);
|
||||||
|
if (student == null) {
|
||||||
|
throw new EntityNotFoundException(String.format("Student with id [%s] is not found", id));
|
||||||
|
}
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<Student> findAllStudents() {
|
||||||
|
return studentRepository.findAll();
|
||||||
|
}
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<Student> findAllFreeStudents() {
|
||||||
|
return studentRepository.findByDrivingSchoolIsNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student updateStudent(Long id, String surname, String name, String phoneNumber) {
|
||||||
|
if (!StringUtils.hasText(name) ||!StringUtils.hasText(surname) || !StringUtils.hasText(phoneNumber)) {
|
||||||
|
throw new IllegalArgumentException("Student's data is null or empty");
|
||||||
|
}
|
||||||
|
final Student currentStudent = findStudent(id);
|
||||||
|
currentStudent.setName(name);
|
||||||
|
currentStudent.setSurname(surname);
|
||||||
|
currentStudent.setPhoneNumber(phoneNumber);
|
||||||
|
|
||||||
|
return studentRepository.save(currentStudent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student deleteStudent(Long id) {
|
||||||
|
final Student student = findStudent(id);
|
||||||
|
studentRepository.delete(student);
|
||||||
|
return student;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteAllStudents() {
|
||||||
|
studentRepository.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void addDrivingSchool(Long id, DrivingSchool c) {
|
||||||
|
final Student student = findStudent(id);
|
||||||
|
student.setDrivingSchool(c);
|
||||||
|
studentRepository.save(student);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteDrivingSchool(Long id) {
|
||||||
|
final Student student = findStudent(id);
|
||||||
|
student.deleteDrivingSchool();
|
||||||
|
studentRepository.save(student);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student addCategory(Long id, Category p) {
|
||||||
|
Student e = findStudent(id);
|
||||||
|
e.addNewCategory(p);
|
||||||
|
System.out.println(e.getCategories().size());
|
||||||
|
return studentRepository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Student deleteCategory(Long id, Category p) {
|
||||||
|
Student e = findStudent(id);
|
||||||
|
e.removeCategory(p);
|
||||||
|
return studentRepository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public List<Student> getStudentsByCategory(Category p) {
|
||||||
|
List<Student> students = studentRepository.findByCategories_Id(p.getId());
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.controller;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.WebConfiguration;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserDto;
|
||||||
|
import ru.ulstu.is.cbapp.user.service.UserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController()
|
||||||
|
public class UserController {
|
||||||
|
public static final String URL_LOGIN = "/jwt/login";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public UserController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(URL_LOGIN)
|
||||||
|
public String login(@RequestBody @Valid UserDto userDto) {
|
||||||
|
return userService.loginAndGetToken(userDto);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.controller;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.user.model.User;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserDto;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserRole;
|
||||||
|
import ru.ulstu.is.cbapp.user.service.UserService;
|
||||||
|
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.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/users")
|
||||||
|
public class UserMvcController {
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public UserMvcController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Secured({UserRole.AsString.ADMIN})
|
||||||
|
public String getUsers(@RequestParam(defaultValue = "1") int page,
|
||||||
|
@RequestParam(defaultValue = "5") int size,
|
||||||
|
Model model) {
|
||||||
|
final Page<UserDto> users = userService.findAllPages(page, size)
|
||||||
|
.map(UserDto::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 "users";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.controller;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.user.model.User;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserSignupDto;
|
||||||
|
import ru.ulstu.is.cbapp.user.service.UserService;
|
||||||
|
import ru.ulstu.is.cbapp.util.validation.ValidationException;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping(UserSignupMvcController.SIGNUP_URL)
|
||||||
|
public class UserSignupMvcController {
|
||||||
|
public static final String SIGNUP_URL = "/signup";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
public UserSignupMvcController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String showSignupForm(Model model) {
|
||||||
|
model.addAttribute("userDto", new UserSignupDto());
|
||||||
|
return "signup";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||||
|
return "signup";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final User user = userService.createUser(
|
||||||
|
userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm());
|
||||||
|
return "redirect:/login?created=" + user.getLogin();
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
model.addAttribute("errors", e.getMessage());
|
||||||
|
return "signup";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/main/java/ru/ulstu/is/cbapp/user/model/User.java
Normal file
75
src/main/java/ru/ulstu/is/cbapp/user/model/User.java
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users")
|
||||||
|
public class User {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
@Column(nullable = false, unique = true, length = 64)
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 3, max = 64)
|
||||||
|
private String login;
|
||||||
|
@Column(nullable = false, length = 64)
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 6, max = 64)
|
||||||
|
private String password;
|
||||||
|
private UserRole role;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(String login, String password) {
|
||||||
|
this(login, password, UserRole.USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(String login, String password, UserRole role) {
|
||||||
|
this.login = login;
|
||||||
|
this.password = password;
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return 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 UserRole getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
User user = (User) o;
|
||||||
|
return Objects.equals(id, user.id) && Objects.equals(login, user.login);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, login);
|
||||||
|
}
|
||||||
|
}
|
46
src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java
Normal file
46
src/main/java/ru/ulstu/is/cbapp/user/model/UserDto.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.model;
|
||||||
|
|
||||||
|
public class UserDto {
|
||||||
|
private long id;
|
||||||
|
private String login;
|
||||||
|
private UserRole role;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public UserDto(User user) {
|
||||||
|
this.id = user.getId();
|
||||||
|
this.login = user.getLogin();
|
||||||
|
this.role = user.getRole();
|
||||||
|
this.password = user.getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogin() {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRole getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDto() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogin(String login) {
|
||||||
|
this.login = login;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(UserRole role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java
Normal file
20
src/main/java/ru/ulstu/is/cbapp/user/model/UserRole.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.model;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum UserRole implements GrantedAuthority {
|
||||||
|
ADMIN,
|
||||||
|
USER;
|
||||||
|
|
||||||
|
private static final String PREFIX = "ROLE_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return PREFIX + this.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class AsString {
|
||||||
|
public static final String ADMIN = PREFIX + "ADMIN";
|
||||||
|
public static final String USER = PREFIX + "USER";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.model;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.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
|
||||||
|
@Size(min = 6, max = 64)
|
||||||
|
private String passwordConfirm;
|
||||||
|
|
||||||
|
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 getPasswordConfirm() {
|
||||||
|
return passwordConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordConfirm(String passwordConfirm) {
|
||||||
|
this.passwordConfirm = passwordConfirm;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.repository;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.user.model.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
User findOneByLoginIgnoreCase(String login);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.service;
|
||||||
|
|
||||||
|
public class UserNotFoundException extends RuntimeException {
|
||||||
|
public UserNotFoundException(String login) {
|
||||||
|
super(String.format("User not found '%s'", login));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package ru.ulstu.is.cbapp.user.service;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.configuration.jwt.JwtException;
|
||||||
|
import ru.ulstu.is.cbapp.configuration.jwt.JwtsProvider;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.User;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserDto;
|
||||||
|
import ru.ulstu.is.cbapp.user.model.UserRole;
|
||||||
|
import ru.ulstu.is.cbapp.user.repository.UserRepository;
|
||||||
|
import ru.ulstu.is.cbapp.util.validation.ValidationException;
|
||||||
|
import ru.ulstu.is.cbapp.util.validation.ValidatorUtil;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserService implements UserDetailsService {
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final ValidatorUtil validatorUtil;
|
||||||
|
private final JwtsProvider jwtProvider;
|
||||||
|
|
||||||
|
public UserService(UserRepository userRepository,
|
||||||
|
PasswordEncoder passwordEncoder,
|
||||||
|
ValidatorUtil validatorUtil,
|
||||||
|
JwtsProvider jwtProvider) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.validatorUtil = validatorUtil;
|
||||||
|
this.jwtProvider = jwtProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<User> findAllPages(int page, int size) {
|
||||||
|
return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public User findByLogin(String login) {
|
||||||
|
return userRepository.findOneByLoginIgnoreCase(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User createUser(String login, String password, String passwordConfirm) {
|
||||||
|
return createUser(login, password, passwordConfirm, UserRole.USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User createUser(String login, String password, String passwordConfirm, UserRole role) {
|
||||||
|
if (findByLogin(login) != null) {
|
||||||
|
throw new ValidationException(String.format("User '%s' already exists", login));
|
||||||
|
}
|
||||||
|
final User user = new User(login, passwordEncoder.encode(password), role);
|
||||||
|
validatorUtil.validate(user);
|
||||||
|
if (!Objects.equals(password, passwordConfirm)) {
|
||||||
|
throw new ValidationException("Passwords not equals");
|
||||||
|
}
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String loginAndGetToken(UserDto userDto) {
|
||||||
|
final User user = findByLogin(userDto.getLogin());
|
||||||
|
if (user == null) {
|
||||||
|
throw new UserNotFoundException(userDto.getLogin());
|
||||||
|
}
|
||||||
|
if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
|
||||||
|
throw new UserNotFoundException(user.getLogin());
|
||||||
|
}
|
||||||
|
return jwtProvider.generateToken(user.getLogin(), user.getRole().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDetails loadUserByToken(String token) throws UsernameNotFoundException {
|
||||||
|
if (!jwtProvider.validateToken(token)) {
|
||||||
|
throw new JwtException("Bad token");
|
||||||
|
}
|
||||||
|
final String userLogin = jwtProvider.getLogin(token);
|
||||||
|
if (userLogin.isEmpty()) {
|
||||||
|
throw new JwtException("Token is not contain Login");
|
||||||
|
}
|
||||||
|
return loadUserByUsername(userLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
final User userEntity = findByLogin(username);
|
||||||
|
if (userEntity == null) {
|
||||||
|
throw new UsernameNotFoundException(username);
|
||||||
|
}
|
||||||
|
return new org.springframework.security.core.userdetails.User(
|
||||||
|
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package ru.ulstu.is.cbapp.util.error;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.util.validation.ValidationException;
|
||||||
|
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import java.nio.file.AccessDeniedException;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ControllerAdvice(annotations = RestController.class)
|
||||||
|
public class AdviceController {
|
||||||
|
@ExceptionHandler({
|
||||||
|
ValidationException.class
|
||||||
|
})
|
||||||
|
public ResponseEntity<Object> handleException(Throwable e) {
|
||||||
|
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({
|
||||||
|
AccessDeniedException.class
|
||||||
|
})
|
||||||
|
public ResponseEntity<Object> handleAccessDeniedException(Throwable e) {
|
||||||
|
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ResponseEntity<Object> handleBindException(MethodArgumentNotValidException e) {
|
||||||
|
final ValidationException validationException = new ValidationException(
|
||||||
|
e.getBindingResult().getAllErrors().stream()
|
||||||
|
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
return handleException(validationException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<Object> handleUnknownException(Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.ulstu.is.cbapp.util.validation;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ValidationException extends RuntimeException {
|
||||||
|
public <T> ValidationException(Set<String> errors) {
|
||||||
|
super(String.join("\n", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> ValidationException(String error) {
|
||||||
|
super(error);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.ulstu.is.cbapp.util.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintViolation;
|
||||||
|
import jakarta.validation.Validation;
|
||||||
|
import jakarta.validation.Validator;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ValidatorUtil {
|
||||||
|
private final Validator validator;
|
||||||
|
|
||||||
|
public ValidatorUtil() {
|
||||||
|
this.validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void validate(T object) {
|
||||||
|
final Set<ConstraintViolation<T>> errors = validator.validate(object);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new ValidationException(errors.stream()
|
||||||
|
.map(ConstraintViolation::getMessage)
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,14 @@
|
|||||||
|
spring.main.banner-mode=off
|
||||||
|
#server.port=8080
|
||||||
|
spring.datasource.url=jdbc:h2:file:./data
|
||||||
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
spring.datasource.username=sa
|
||||||
|
spring.datasource.password=password
|
||||||
|
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.h2.console.enabled=true
|
||||||
|
spring.h2.console.settings.trace=false
|
||||||
|
spring.h2.console.settings.web-allow-others=false
|
||||||
|
jwt.dev-token=my-secret-jwt
|
||||||
|
jwt.dev=true
|
||||||
|
jwt.secret = my-secret-jwt
|
||||||
|
9
src/main/resources/static/styles/drivingSchool.css
Normal file
9
src/main/resources/static/styles/drivingSchool.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.prokrutka {
|
||||||
|
background: #fff; /* цвет фона, белый */
|
||||||
|
border: 1px solid #C1C1C1; /* размер и цвет границы блока */
|
||||||
|
overflow: auto;
|
||||||
|
width:230px;
|
||||||
|
height:100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
21
src/main/resources/static/styles/login.css
Normal file
21
src/main/resources/static/styles/login.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0069d9;
|
||||||
|
border-color: #0062cc;
|
||||||
|
}
|
115
src/main/resources/templates/category.html
Normal file
115
src/main/resources/templates/category.html
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<div>Категории</div>
|
||||||
|
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||||
|
<a class="btn btn-success button-fixed"
|
||||||
|
onsubmit="openModalEdit()" data-bs-target="#categoryEditModal" data-bs-toggle="modal">
|
||||||
|
<i class="fa-solid fa-plus"></i> Добавить
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Название</th>
|
||||||
|
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="category, iterator: ${categories}">
|
||||||
|
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||||
|
<td th:text="${category.id}"/>
|
||||||
|
<td th:text="${category.name}" style="width: 60%"/>
|
||||||
|
<td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<a class="btn btn-info button-fixed button-sm" th:data-category="${category.id}"
|
||||||
|
data-bs-target="#categoryEditModal" data-bs-toggle="modal"
|
||||||
|
th:onclick="openModalEdit(this.getAttribute('data-category'))">Изменить</a>
|
||||||
|
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||||
|
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${category.id}').click()|">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form th:action="@{/category/delete/{id}(id=${category.id})}" method="post">
|
||||||
|
<button th:id="'remove-' + ${category.id}" type="submit" style="display: none">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="categoryEditModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-edit-category">
|
||||||
|
<input type="hidden" id="categoryId" name="id"/>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Название</label>
|
||||||
|
<input type="text" class="form-control" id="name" required="true">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary button-fixed">
|
||||||
|
<span th:if="${id == null}">Добавить</span>
|
||||||
|
<span th:if="${id != null}">Обновить</span>
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<th:block layout:fragment="scripts">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
function openModalEdit(id) {
|
||||||
|
var categories = [[${categories}]];
|
||||||
|
var category = categories.find(x => x.id + '' === id);
|
||||||
|
if (category !== 'undefined') {
|
||||||
|
$('#categoryId').val(id);
|
||||||
|
$('#name').val(category.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('submit', '#form-edit-category', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var categoryName = document.getElementById('name');
|
||||||
|
var idInput = document.getElementById('categoryId');
|
||||||
|
var urlCategory = 'http://localhost:8080/api/category';
|
||||||
|
let category = {name: categoryName.value};
|
||||||
|
var method = 'POST';
|
||||||
|
if (idInput.value !== '') {
|
||||||
|
method = 'PUT';
|
||||||
|
urlCategory = urlCategory + '/' + idInput.value;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: urlCategory,
|
||||||
|
method: method,
|
||||||
|
data: JSON.stringify(category),
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
48
src/main/resources/templates/default.html
Normal file
48
src/main/resources/templates/default.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru"
|
||||||
|
xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<title>Автошколы, студенты и категории</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<link rel="icon" href="/favicon.svg">
|
||||||
|
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
|
||||||
|
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
|
||||||
|
<script type="text/javascript" src="/webjars/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="lg justify-content-center container">
|
||||||
|
<button class="navbar-toggler" type="button"
|
||||||
|
data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav" th:with="activeLink=${requestURI}">
|
||||||
|
<a class="nav-link" href="/drivingSchool"
|
||||||
|
th:classappend="${#strings.equals(activeLink, '/')} ? 'active' : ''">Главная страница</a>
|
||||||
|
<a class="nav-link" href="/student"
|
||||||
|
th:classappend="${#strings.equals(activeLink, '/student')} ? 'active' : ''">Студенты</a>
|
||||||
|
<a class="nav-link" href="/drivingSchool"
|
||||||
|
th:classappend="${#strings.equals(activeLink, '/drivingSchool')} ? 'active' : ''">Автошколы</a>
|
||||||
|
<a class="nav-link" href="/category"
|
||||||
|
th:classappend="${#strings.equals(activeLink, '/category')} ? 'active' : ''">Категории</a>
|
||||||
|
<a class="nav-link" href="/categoryStudent/groupbycategory"
|
||||||
|
th:classappend="${#strings.equals(activeLink, '/categoryStudent/groupbycategory')} ? 'active' : ''">Количество студентов в категории</a>
|
||||||
|
<a sec:authorize="!isAuthenticated()" class="nav-link" href="/login">Войти</a>
|
||||||
|
<a sec:authorize="isAuthenticated()" class="nav-link" href="/logout">Выйти</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container-lg pt-5 min-vh-100">
|
||||||
|
<div class="container container-padding" layout:fragment="content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<th:block layout:fragment="scripts">
|
||||||
|
</th:block>
|
||||||
|
</html>
|
255
src/main/resources/templates/drivingSchool-one.html
Normal file
255
src/main/resources/templates/drivingSchool-one.html
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<body>
|
||||||
|
<head>
|
||||||
|
<link th:href="@{/styles/drivingSchool.css}" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<h1>Название: <span th:text="${drivingSchool.name}"></span></h1>
|
||||||
|
<h2>Количество студентов: <span th:text="${#arrays.length(drivingSchool.students.toArray())}"></span></h2>
|
||||||
|
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||||
|
<a class="btn btn-success button-fixed"
|
||||||
|
data-bs-target="#hireModal" data-bs-toggle="modal">
|
||||||
|
Зачислить студента
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Фамилия</th>
|
||||||
|
<th scope="col">Имя</th>
|
||||||
|
<th scope="col">Номер телефона</th>
|
||||||
|
<th scope="col">Категории</th>
|
||||||
|
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="student, iterator: ${drivingSchool.students}">
|
||||||
|
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||||
|
<td th:text="${student.id}"/>
|
||||||
|
<td th:text="${student.surname}" />
|
||||||
|
<td th:text="${student.name}" />
|
||||||
|
<td th:text="${student.phoneNumber}" />
|
||||||
|
<td th:text="${#strings.listJoin(student.categories.![name],',')}"></td>
|
||||||
|
<th sec:authorize="hasRole('ROLE_ADMIN')" style="width: 20%">
|
||||||
|
<a class="btn btn-danger button-fixed button-sm" th:data-student="${student.id}"
|
||||||
|
data-bs-target="#dismissModal" data-bs-toggle="modal"
|
||||||
|
th:onclick="openModalDismiss(this.getAttribute('data-student'))">Отчислить</a>
|
||||||
|
<a class="btn btn-info button-fixed button-sm" th:data-student="${student.id}"
|
||||||
|
data-bs-target="#manageCategoryModal" data-bs-toggle="modal"
|
||||||
|
th:onclick="openModalManageCategory(this.getAttribute('data-student'))">Категории</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal" id="dismissModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Отчисление</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-dismiss">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="studentSelect">Студент</label>
|
||||||
|
<select class="form-select" id="studentSelect" name="studentId" required>
|
||||||
|
<option th:each="student, iterator: ${drivingSchool.students}" th:value="${student.id}"
|
||||||
|
th:text="${student.surname} + ' ' + ${student.name}">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-danger" type="submit">Отчислить</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="hireModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="hireModalLabel">Зачислить</h5>
|
||||||
|
<button type="button" class="close" data-bs-dismiss="modal">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-hire">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="studentHireSelect">Студент</label>
|
||||||
|
<select class="form-select" id="studentHireSelect" name="studentId" required>
|
||||||
|
<option selected disabled>Выберите студента</option>
|
||||||
|
<option th:each="student, iterator: ${freeStudents}" th:value="${student.id}"
|
||||||
|
th:text="${student.surname} + ' ' + ${student.name}">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-info" type="submit">Зачислить</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="manageCategoryModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="manageCategoryModalLabel">Назначение категории</h5>
|
||||||
|
<button type="button" class="close" data-bs-dismiss="modal">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-categories">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="studentCatSelect">Студент</label>
|
||||||
|
<select class="form-select" id="studentCatSelect" name="studentId" required>
|
||||||
|
<option value="0" selected disabled>Выберите студента</option>
|
||||||
|
<option th:each="student, iterator: ${drivingSchool.students}" th:value="${student.id}"
|
||||||
|
th:text="${student.surname} + ' ' + ${student.name}">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="studentSelect">Категории</label>
|
||||||
|
<div class="prokrutka">
|
||||||
|
<div style="200px;" th:each="category, iterator: ${categories}">
|
||||||
|
<input type="checkbox" th:value="${category.id}"
|
||||||
|
th:text="${category.name}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-info" type="submit">Зачислить</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<th:block layout:fragment="scripts">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
$(document).on('submit', '#form-dismiss', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var drivingSchool = [[${drivingSchool}]];
|
||||||
|
var url = 'http://localhost:8080/api/drivingSchool/id/dismiss?studentId=studId';
|
||||||
|
url = url.replace("id", drivingSchool.id).replace("studId", $("#studentSelect").val())
|
||||||
|
alert(url);
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: 'PUT',
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(document).on('submit', '#form-hire', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var drivingSchool = [[${drivingSchool}]];
|
||||||
|
var url = 'http://localhost:8080/api/drivingSchool/id/hire?studentId=studId';
|
||||||
|
url = url.replace("id", drivingSchool.id).replace("studId", $("#studentHireSelect").val())
|
||||||
|
alert(url);
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: 'PUT',
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(document).on('submit', '#form-categories', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var drivingSchool = [[${drivingSchool}]];
|
||||||
|
var studentId = $("#studentCatSelect").val();
|
||||||
|
var student = drivingSchool.students.find(x => x.id + '' === studentId);
|
||||||
|
var categories = student.categories.map(x => x.id + '');
|
||||||
|
var urlAdd = 'http://localhost:8080/api/student/id/addCat?category=catId';
|
||||||
|
var urlDel = 'http://localhost:8080/api/student/id/delCat?category=catId';
|
||||||
|
$('input[type=checkbox]').each(function(){
|
||||||
|
if(categories.indexOf($(this).val()) !== -1 && $(this).is(":checked") === false){
|
||||||
|
$.ajax({
|
||||||
|
url: urlDel.replace("id", student.id).replace("catId", $(this).val()),
|
||||||
|
method: 'PUT',
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (categories.indexOf($(this).val()) == -1 && $(this).is(":checked")){
|
||||||
|
$.ajax({
|
||||||
|
url: urlAdd.replace("id", student.id).replace("catId", $(this).val()),
|
||||||
|
method: 'PUT',
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#studentCatSelect').change(function(){
|
||||||
|
var selectedStudent = $(this).val();
|
||||||
|
var drivingSchool = [[${drivingSchool}]];
|
||||||
|
var student = drivingSchool.students.find(x => x.id + '' === selectedStudent);
|
||||||
|
var categories = student.categories.map(x => x.id + '');
|
||||||
|
$('input[type=checkbox]').prop('checked', false);
|
||||||
|
$('input[type=checkbox]').each(function(){
|
||||||
|
if(categories.indexOf($(this).val()) !== -1){
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function openModalDismiss(id) {
|
||||||
|
$('#studentSelect').val(id);
|
||||||
|
$( "#studentSelect" ).prop( "disabled", true );
|
||||||
|
}
|
||||||
|
function openModalManageCategory(id) {
|
||||||
|
$('#studentCatSelect').val(id);
|
||||||
|
$( "#studentCatSelect" ).prop( "disabled", true );
|
||||||
|
var drivingSchool = [[${drivingSchool}]];
|
||||||
|
var student = drivingSchool.students.find(x => x.id + '' === id);
|
||||||
|
var categories = student.categories.map(x => x.id + '');
|
||||||
|
$('input[type=checkbox]').prop('checked', false);
|
||||||
|
$('input[type=checkbox]').each(function(){
|
||||||
|
if(categories.indexOf($(this).val()) !== -1){
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
145
src/main/resources/templates/drivingSchool.html
Normal file
145
src/main/resources/templates/drivingSchool.html
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<div>Автошколы</div>
|
||||||
|
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||||
|
<a class="btn btn-success button-fixed"
|
||||||
|
onsubmit="openModalEdit()" data-bs-target="#drivingSchoolEditModal" data-bs-toggle="modal">
|
||||||
|
<i class="fa-solid fa-plus"></i> Добавить
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Название</th>
|
||||||
|
<th scope="col">Студенты</th>
|
||||||
|
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="drivingSchool, iterator: ${drivingSchools}">
|
||||||
|
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||||
|
<td th:text="${drivingSchool.id}"/>
|
||||||
|
<td style="width: 60%"><a th:href="@{/drivingSchool/one/{id}(id=${drivingSchool.id})}" th:text="${drivingSchool.name}"></a></td>
|
||||||
|
<td th:text="${#arrays.length(drivingSchool.students.toArray())}" style="width: 60%"/>
|
||||||
|
<td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<a class="btn btn-info button-fixed button-sm" th:data-drivingSchool="${drivingSchool.id}"
|
||||||
|
data-bs-target="#drivingSchoolEditModal" data-bs-toggle="modal"
|
||||||
|
th:onclick="openModalEdit(this.getAttribute('data-drivingSchool'))">Изменить</a>
|
||||||
|
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||||
|
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${drivingSchool.id}').click()|">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form th:action="@{/drivingSchool/delete/{id}(id=${drivingSchool.id})}" method="post">
|
||||||
|
<button th:id="'remove-' + ${drivingSchool.id}" type="submit" style="display: none">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal" id="drivingSchoolModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Выбор автошколы</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="/drivingSchool/redirect" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="drivingSchoolSelect">Автошкола</label>
|
||||||
|
<select class="form-select" id="drivingSchoolSelect" name="drivingSchoolId" required>
|
||||||
|
<option selected disabled>Выберите автошколу</option>
|
||||||
|
<option th:each="drivingSchool, iterator: ${drivingSchools}" th:value="${drivingSchool.id}" th:text="${drivingSchool.name}">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="submit">Перейти</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="drivingSchoolEditModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-edit-drivingSchool">
|
||||||
|
<input type="hidden" id="drivingSchoolId" name="id"/>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Название</label>
|
||||||
|
<input type="text" class="form-control" id="name" required="true">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary button-fixed">
|
||||||
|
<span th:if="${id == null}">Добавить</span>
|
||||||
|
<span th:if="${id != null}">Обновить</span>
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<th:block layout:fragment="scripts">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
function openModalEdit(id) {
|
||||||
|
var drivingSchools = [[${drivingSchools}]];
|
||||||
|
var drivingSchool = drivingSchools.find(x => x.id + '' === id);
|
||||||
|
if (drivingSchool !== 'undefined') {
|
||||||
|
$('#drivingSchoolId').val(id);
|
||||||
|
$('#name').val(drivingSchool.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('submit', '#form-edit-drivingSchool', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var drivingSchoolName = document.getElementById('name');
|
||||||
|
var idInput = document.getElementById('drivingSchoolId');
|
||||||
|
var urlDrivingSchool = 'http://localhost:8080/api/drivingSchool';
|
||||||
|
let drivingSchool = {name: drivingSchoolName.value};
|
||||||
|
var method = 'POST';
|
||||||
|
if (idInput.value !== '') {
|
||||||
|
method = 'PUT';
|
||||||
|
urlDrivingSchool = urlDrivingSchool + '/' + idInput.value;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: urlDrivingSchool,
|
||||||
|
method: method,
|
||||||
|
data: JSON.stringify(drivingSchool),
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
13
src/main/resources/templates/error.html
Normal file
13
src/main/resources/templates/error.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<div><span th:text="${error}"></span></div>
|
||||||
|
<a href="/">На главную</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
27
src/main/resources/templates/groupbycategory.html
Normal file
27
src/main/resources/templates/groupbycategory.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<h1>Отчет (количество студентов в категории)</h1>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Название категории</th>
|
||||||
|
<th scope="col">Количество студентов</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="GroupedStudAndCategoryDto : ${GroupedStudAndCategoryDtos}">
|
||||||
|
<td th:text="${GroupedStudAndCategoryDto.categoryName}"></td>
|
||||||
|
<td th:text="${GroupedStudAndCategoryDto.studentsCount}"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
src/main/resources/templates/index.html
Normal file
12
src/main/resources/templates/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<div>Добро пожаловать! Выберите страницу.</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
43
src/main/resources/templates/login.html
Normal file
43
src/main/resources/templates/login.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
<link th:href="@{/styles/login.css}" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid" layout:fragment="content">
|
||||||
|
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
|
||||||
|
Пользователь не найден или пароль указан не верно
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
|
||||||
|
Выход успешно произведен
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.created}" class="alert alert-success margin-bottom">
|
||||||
|
Пользователь '<span th:text="${param.created}"></span>' успешно создан
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center align-items-center vh-100">
|
||||||
|
<div class="col-sm-6 col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Авторизация</h5>
|
||||||
|
<form th:action="@{/login}" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label htmlFor="login">Логин</label>
|
||||||
|
<input type="text" name="username" id="username" class="form-control"
|
||||||
|
placeholder="Логин" required="true" autofocus="true"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label htmlFor="login">Пароль</label>
|
||||||
|
<input type="password" name="password" id="password" class="form-control"
|
||||||
|
placeholder="Пароль" required="true"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary button-fixed">Войти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
28
src/main/resources/templates/signup.html
Normal file
28
src/main/resources/templates/signup.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<body>
|
||||||
|
<div class="container container-padding" layout:fragment="content">
|
||||||
|
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
|
||||||
|
<form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="text" class="form-control" th:field="${userDto.login}"
|
||||||
|
placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="password" class="form-control" th:field="${userDto.password}"
|
||||||
|
placeholder="Пароль" required="true" minlength="6" maxlength="64"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="password" class="form-control" th:field="${userDto.passwordConfirm}"
|
||||||
|
placeholder="Пароль (подтверждение)" required="true" minlength="6" maxlength="64"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-success button-fixed">Создать</button>
|
||||||
|
<a class="btn btn-primary button-fixed" href="/login">Назад</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
133
src/main/resources/templates/student.html
Normal file
133
src/main/resources/templates/student.html
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div layout:fragment="content">
|
||||||
|
<div>Студенты</div>
|
||||||
|
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||||
|
<a class="btn btn-success button-fixed"
|
||||||
|
onsubmit="openModalEdit()" data-bs-target="#studentEditModal" data-bs-toggle="modal">
|
||||||
|
<i class="fa-solid fa-plus"></i> Добавить
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Фамилия</th>
|
||||||
|
<th scope="col">Имя</th>
|
||||||
|
<th scope="col">Номер телефона</th>
|
||||||
|
<th scope="col">Категории</th>
|
||||||
|
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col">Элементы управления</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="student, iterator: ${students}">
|
||||||
|
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||||
|
<td th:text="${student.id}"/>
|
||||||
|
<td th:text="${student.surname}" />
|
||||||
|
<td th:text="${student.name}" />
|
||||||
|
<td th:text="${student.phoneNumber}" />
|
||||||
|
<td th:text="${#strings.listJoin(student.categories.![name],',')}"></td>
|
||||||
|
<td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<a class="btn btn-info button-fixed button-sm" th:data-student="${student.id}"
|
||||||
|
data-bs-target="#studentEditModal" data-bs-toggle="modal"
|
||||||
|
th:onclick="openModalEdit(this.getAttribute('data-student'))">
|
||||||
|
Изменить
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-danger button-fixed button-sm"
|
||||||
|
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${student.id}').click()|">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form th:action="@{/student/delete/{id}(id=${student.id})}" method="post">
|
||||||
|
<button th:id="'remove-' + ${student.id}" type="submit" style="display: none">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="studentEditModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="form-edit-student">
|
||||||
|
<input type="hidden" id="studentId" name="id"/>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="surname" class="form-label">Фамилия</label>
|
||||||
|
<input type="text" class="form-control" id="surname" required="true">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Имя</label>
|
||||||
|
<input type="text" class="form-control" id="name" required="true">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="phoneNumber" class="form-label">Номер телефона</label>
|
||||||
|
<input type="text" class="form-control" id="phoneNumber" required="true">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary button-fixed">
|
||||||
|
<span th:if="${id == null}">Добавить</span>
|
||||||
|
<span th:if="${id != null}">Обновить</span>
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-secondary button-fixed" data-bs-dismiss="modal">
|
||||||
|
Назад
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<th:block layout:fragment="scripts">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
function openModalEdit(id) {
|
||||||
|
var students = [[${students}]];
|
||||||
|
var student = students.find(x => x.id + '' === id);
|
||||||
|
if (student !== 'undefined') {
|
||||||
|
$('#studentId').val(id);
|
||||||
|
$('#surname').val(student.surname);
|
||||||
|
$('#name').val(student.name);
|
||||||
|
$('#phoneNumber').val(student.phoneNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('submit', '#form-edit-student', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var idInput = document.getElementById('studentId');
|
||||||
|
var urlStudent = 'http://localhost:8080/api/student';
|
||||||
|
let student = {surname: $('#surname').val(),
|
||||||
|
name: $('#name').val(),
|
||||||
|
phoneNumber: $('#phoneNumber').val()};
|
||||||
|
var method = 'POST';
|
||||||
|
if (idInput.value !== '') {
|
||||||
|
method = 'PUT';
|
||||||
|
urlStudent = urlStudent + '/' + idInput.value;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: urlStudent,
|
||||||
|
method: method,
|
||||||
|
data: JSON.stringify(student),
|
||||||
|
contentType: "application/json;"
|
||||||
|
})
|
||||||
|
.done(function() {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</html>
|
37
src/main/resources/templates/users.html
Normal file
37
src/main/resources/templates/users.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{default}">
|
||||||
|
<body>
|
||||||
|
<div class="container" layout:fragment="content">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Логин</th>
|
||||||
|
<th scope="col">Роль</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="user, iterator: ${users}">
|
||||||
|
<th scope="row" th:text="${iterator.index} + 1"></th>
|
||||||
|
<td th:text="${user.id}"></td>
|
||||||
|
<td th:text="${user.login}" style="width: 60%"></td>
|
||||||
|
<td th:text="${user.role}" style="width: 20%"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div th:if="${totalPages > 0}" class="pagination">
|
||||||
|
<span style="float: left; padding: 5px 5px;">Страницы:</span>
|
||||||
|
<a th:each="page : ${pages}"
|
||||||
|
th:href="@{/users(page=${page}, size=${users.size})}"
|
||||||
|
th:text="${page}"
|
||||||
|
th:class="${page == users.number + 1} ? active">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
55
src/test/java/ru/ulstu/is/cbapp/CategoryServiceTests.java
Normal file
55
src/test/java/ru/ulstu/is/cbapp/CategoryServiceTests.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package ru.ulstu.is.cbapp;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class CategoryServiceTests {
|
||||||
|
@Autowired
|
||||||
|
private CategoryService categoryService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StudentService studentService;
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
|
||||||
|
Category p = categoryService.addCategory("Category");
|
||||||
|
Student e = studentService.addStudent("1", "2", "33");
|
||||||
|
|
||||||
|
studentService.addCategory(e.getId(), p);
|
||||||
|
Assertions.assertTrue(studentService.findStudent(e.getId()).getCategories().contains(p));
|
||||||
|
Assertions.assertTrue(categoryService.findCategory(p.getId()).getStudents().contains(e));
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
|
||||||
|
Category p = categoryService.addCategory("Category");
|
||||||
|
Student e = studentService.addStudent("1", "2", "33");
|
||||||
|
|
||||||
|
studentService.addCategory(e.getId(), p);
|
||||||
|
studentService.deleteCategory(e.getId(), p);
|
||||||
|
Assertions.assertFalse(studentService.findStudent(e.getId()).getCategories().contains(p));
|
||||||
|
Assertions.assertFalse(categoryService.findCategory(p.getId()).getStudents().contains(e));
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package ru.ulstu.is.cbapp;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class CbappApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void contextLoads() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
116
src/test/java/ru/ulstu/is/cbapp/DrivingSchoolServiceTests.java
Normal file
116
src/test/java/ru/ulstu/is/cbapp/DrivingSchoolServiceTests.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package ru.ulstu.is.cbapp;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import ru.ulstu.is.cbapp.service.DrivingSchoolService;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class DrivingSchoolServiceTests {
|
||||||
|
@Autowired
|
||||||
|
private DrivingSchoolService drivingSchoolService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StudentService studentService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteAllDrivingSchools() {
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
int n = 3;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
String name = "DrivingSchool" + i;
|
||||||
|
drivingSchoolService.addDrivingSchool(name);
|
||||||
|
}
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
Assertions.assertEquals(drivingSchoolService.findAllDrivingSchools().size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteDrivingSchool() {
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
String name = "DrivingSchool1";
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool(name);
|
||||||
|
drivingSchoolService.deleteDrivingSchool(c.getId());
|
||||||
|
Assertions.assertThrows(EntityNotFoundException.class, () -> {
|
||||||
|
drivingSchoolService.findDrivingSchool(c.getId());});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddDrivingSchool() {
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
String name = "DrivingSchool1";
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool(name);
|
||||||
|
Assertions.assertNotNull(drivingSchoolService.findDrivingSchool(c.getId()));
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDrivingSchool() {
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
String name = "DrivingSchool1";
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool(name);
|
||||||
|
String name2 = "DrivingSchool2";
|
||||||
|
drivingSchoolService.updateDrivingSchool(c.getId(), name2);
|
||||||
|
Assertions.assertNotNull(drivingSchoolService.findDrivingSchool(c.getId()));
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindAllDrivingSchools() {
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
int n = 3;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
String name = "DrivingSchool" + i;
|
||||||
|
drivingSchoolService.addDrivingSchool(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertEquals(drivingSchoolService.findAllDrivingSchools().size(), n);
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStudent() {
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
|
||||||
|
|
||||||
|
final String name = "DrivingSchool";
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool(name);
|
||||||
|
Student newStudent = studentService.addStudent("cha", "chacha", "111");
|
||||||
|
drivingSchoolService.addNewStudent(c.getId(), newStudent);
|
||||||
|
|
||||||
|
Assertions.assertTrue(drivingSchoolService.findDrivingSchool(c.getId()).getStudents().contains(newStudent));
|
||||||
|
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
|
||||||
|
|
||||||
|
final String name = "DrivingSchool";
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool(name);
|
||||||
|
Student newStudent = studentService.addStudent("cha", "chacha", "111");
|
||||||
|
|
||||||
|
drivingSchoolService.addNewStudent(c.getId(), newStudent);
|
||||||
|
Assertions.assertTrue(drivingSchoolService.findDrivingSchool(c.getId()).getStudents().contains(newStudent));
|
||||||
|
|
||||||
|
drivingSchoolService.deleteStudent(c.getId(), newStudent);
|
||||||
|
Assertions.assertFalse(drivingSchoolService.findDrivingSchool(c.getId()).getStudents().contains(newStudent));
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
123
src/test/java/ru/ulstu/is/cbapp/StudentServiceTests.java
Normal file
123
src/test/java/ru/ulstu/is/cbapp/StudentServiceTests.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package ru.ulstu.is.cbapp;
|
||||||
|
|
||||||
|
import ru.ulstu.is.cbapp.models.Category;
|
||||||
|
import ru.ulstu.is.cbapp.models.DrivingSchool;
|
||||||
|
import ru.ulstu.is.cbapp.models.Student;
|
||||||
|
import ru.ulstu.is.cbapp.service.DrivingSchoolService;
|
||||||
|
import ru.ulstu.is.cbapp.service.StudentService;
|
||||||
|
import ru.ulstu.is.cbapp.service.CategoryService;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class StudentServiceTests {
|
||||||
|
@Autowired
|
||||||
|
private StudentService studentService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DrivingSchoolService drivingSchoolService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CategoryService categoryService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
|
||||||
|
studentService.addStudent("name", "surname", "111");
|
||||||
|
Assertions.assertEquals(1, studentService.findAllStudents().size());
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
final String newName = "name";
|
||||||
|
|
||||||
|
Student e = studentService.addStudent("surname", "name", "111");
|
||||||
|
studentService.updateStudent(e.getId(),e.getSurname(), newName, e.getPhoneNumber());
|
||||||
|
Assertions.assertEquals(newName, studentService.findStudent(e.getId()).getName());
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
|
||||||
|
Student e = studentService.addStudent("name", "surname", "111");
|
||||||
|
studentService.deleteStudent(e.getId());
|
||||||
|
Assertions.assertEquals(0, studentService.findAllStudents().size());
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindAllStudent() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
int n = 3;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
studentService.addStudent("name", "surname", "111");
|
||||||
|
}
|
||||||
|
Assertions.assertEquals(n, studentService.findAllStudents().size());
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddDrivingSchool() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
|
||||||
|
Student e = studentService.addStudent("name", "surname", "111");
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool("Comp");
|
||||||
|
drivingSchoolService.addNewStudent(c.getId(), e);
|
||||||
|
Assertions.assertEquals(c, studentService.findStudent(e.getId()).getDrivingSchool());
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteFromDrivingSchool() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
|
||||||
|
Student e = studentService.addStudent("name", "surname", "111");
|
||||||
|
DrivingSchool c = drivingSchoolService.addDrivingSchool("Comp");
|
||||||
|
|
||||||
|
studentService.addDrivingSchool(e.getId(), c);
|
||||||
|
Assertions.assertEquals(c, studentService.findStudent(e.getId()).getDrivingSchool());
|
||||||
|
|
||||||
|
studentService.deleteDrivingSchool(e.getId());
|
||||||
|
Assertions.assertNull(studentService.findStudent(e.getId()).getDrivingSchool());
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
drivingSchoolService.deleteAllDrivingSchools();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetByCategory() {
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
|
||||||
|
Category p = categoryService.addCategory("Category 1");
|
||||||
|
Category p2 = categoryService.addCategory("Category 2");
|
||||||
|
|
||||||
|
final int n = 10;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
Student e = studentService.addStudent("1111", "name" + i, "surname");
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
studentService.addCategory(e.getId(), p);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
studentService.addCategory(e.getId(), p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertEquals(n / 2, studentService.getStudentsByCategory(p).size());
|
||||||
|
Assertions.assertEquals(n / 2, studentService.getStudentsByCategory(p2).size());
|
||||||
|
|
||||||
|
studentService.deleteAllStudents();
|
||||||
|
categoryService.deleteAllCategories();
|
||||||
|
}
|
||||||
|
}
|
BIN
Отчеты/1-Жимолостнова Анна Васильевна.docx
Normal file
BIN
Отчеты/1-Жимолостнова Анна Васильевна.docx
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user