Compare commits

...

9 Commits
master ... mvc

Author SHA1 Message Date
Katerina881
5af0868021 Lab5: Finished 2023-04-18 13:02:39 +04:00
4410a17fcf Lab5: Some mvc completed, feed.html remains 2023-04-18 04:13:40 +04:00
535cca10bd Lab5: Completed Customer mvc 2023-04-18 03:15:48 +04:00
7cb753ed2e Lab5: Completed first stage(added @RequestBody and some other things) 2023-04-18 00:04:42 +04:00
Katerina881
2224416988 Lab4 is done 2023-04-04 12:03:56 +04:00
00c99afbcf Frontend is finished(I think) 2023-04-04 00:34:13 +04:00
Katerina881
cbf4b0b9a3 Lab4 in progress 2023-03-21 10:21:00 +04:00
Katerina881
2096215195 Lab3 is done 2023-03-07 12:31:12 +04:00
Katerina881
8315a23855 Lab2 2023-02-21 11:20:02 +04:00
56 changed files with 20034 additions and 234 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/frontend/node_modules/
/build
/.idea

View File

@ -14,7 +14,17 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
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 group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.hibernate.validator:hibernate-validator'
}
tasks.named('test') {

17
frontend/README.md Normal file
View File

@ -0,0 +1,17 @@
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
frontend/babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Калькулятор</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<div class="m-auto dflex flex-row align-items-center">
<div class="col-sm-3 my-1">
<input type="number" class="form-control" id="num1">
</div>
<div class="col-sm-3 my-1">
<select class="form-control" id="operation">
<option value="sum" selected>+</option>
<option value="sub">-</option>
<option value="mul">*</option>
<option value="div">/</option>
</select>
</div>
<div class="col-sm-3 my-1">
<input type="number" class="form-control" id="num2">
</div>
<div class="col-sm-3 my-1">
<button type="button" class="form-control btn btn-light" id="get-result" onclick="calculate()">=</button>
</div>
<div class="col-sm-3 my-1">
<p class="h5" id="result"></p>
</div>
</div>
<script>
async function calculate() {
const num1 = document.getElementById("num1").value
const num2 = document.getElementById("num2").value
document.getElementById("result").innerHTML = await (await fetch(`http://127.0.0.1:8080/${document.getElementById("operation").value}?num1=${num1}&num2=${num2}`)).text()
}
</script>
</body>
</html>

View File

@ -1,12 +0,0 @@
const fs = require('fs')
const http = require('http')
const requestListener = async function (req, res) {
res.writeHead(200);
fs.readFile('index.html', (err, data) => {
res.end(data);
});
};
const server = http.createServer(requestListener)
server.listen(5050)

19
frontend/jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

9
frontend/node_modules/fs/README.md generated vendored
View File

@ -1,9 +0,0 @@
# Security holding package
This package name is not currently in use, but was formerly occupied
by another package. To avoid malicious use, npm is hanging on to the
package name, but loosely, and we'll probably give it to you if you
want it.
You may adopt this package by contacting support@npmjs.com and
requesting the name.

View File

@ -1,46 +0,0 @@
{
"_from": "fs",
"_id": "fs@0.0.1-security",
"_inBundle": false,
"_integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
"_location": "/fs",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "fs",
"name": "fs",
"escapedName": "fs",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"_shasum": "8a7bd37186b6dddf3813f23858b57ecaaf5e41d4",
"_spec": "fs",
"_where": "C:\\Users\\user\\Desktop\\something\\frontend",
"author": "",
"bugs": {
"url": "https://github.com/npm/security-holder/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "This package name is not currently in use, but was formerly occupied by another package. To avoid malicious use, npm is hanging on to the package name, but loosely, and we'll probably give it to you if you want it.",
"homepage": "https://github.com/npm/security-holder#readme",
"keywords": [],
"license": "ISC",
"main": "index.js",
"name": "fs",
"repository": {
"type": "git",
"url": "git+https://github.com/npm/security-holder.git"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"version": "0.0.1-security"
}

View File

@ -1,9 +0,0 @@
# Security holding package
This package name is not currently in use, but was formerly occupied
by another package. To avoid malicious use, npm is hanging on to the
package name, but loosely, and we'll probably give it to you if you
want it.
You may adopt this package by contacting support@npmjs.com and
requesting the name.

View File

@ -1,39 +0,0 @@
{
"_from": "http",
"_id": "http@0.0.1-security",
"_inBundle": false,
"_integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==",
"_location": "/http",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "http",
"name": "http",
"escapedName": "http",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
"_shasum": "3aac09129d12dc2747bbce4157afde20ad1f7995",
"_spec": "http",
"_where": "C:\\Users\\user\\Desktop\\something\\frontend",
"bugs": {
"url": "https://github.com/npm/security-holder/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "security holding package",
"homepage": "https://github.com/npm/security-holder#readme",
"name": "http",
"repository": {
"type": "git",
"url": "git+https://github.com/npm/security-holder.git"
},
"version": "0.0.1-security"
}

17565
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,29 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"name": "front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "node index.js"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"author": "",
"license": "ISC",
"dependencies": {
"fs": "0.0.1-security",
"http": "0.0.1-security"
}
"@popperjs/core": "^2.11.7",
"axios": "^1.3.4",
"core-js": "^3.8.3",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/chota@latest">
<title>Социальная сеть</title>
</head>
<body class="container">
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

47
frontend/src/App.vue Normal file
View File

@ -0,0 +1,47 @@
<template>
<div class="nav row">
<div class="nav-left col-10">
<router-link to="/customers" class="button primary clear">Профили</router-link>
<router-link to="/posts" class="button primary clear">Посты</router-link>
</div>
<div class="nav-right col-2">
<select class="form-select" style="font-size: 16px;" v-model="currentCustomerId">
<option value="-1" selected class="button dark outline" :on-click="updateCurrentCustomer()">Не выбран</option>
<option v-for="customer in customers" v-bind:value="customer['id']" class="button dark outline" :on-click="updateCurrentCustomer()">{{ customer['username'] }}</option>
</select>
</div>
</div>
<router-view></router-view>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
currentCustomerId: -1,
customers: []
}
},
methods: {
async updateCurrentCustomer() {
history.replaceState(this.currentCustomerId, "");
}
},
async beforeMount() {
setInterval(async () => {
const response = await axios.get('http://localhost:8080/api/customer');
this.customers = [];
response.data.forEach(element => {
this.customers.push(element);
console.log(element);
});
}, 500)
},
}
</script>
<style lang="">
</style>

View File

@ -0,0 +1,172 @@
<template>
<div class="row">
<div class="col">
<div class="row mb-5">
<p class='is-center h2'>Профили</p>
</div>
<div class="row mb-5">
<div class="col"></div>
<div class="col-10 is-center">
<button class="button primary" data-bs-toggle="modal" data-bs-target="#customerCreate">
Добавить нового пользователя
</button>
</div>
<div class="col"></div>
</div>
<p class='h3 is-center row mb-5'>Список профилей</p>
<div class="row">
<div class="col">
<div v-for="customer in customers" class="row card mb-3">
<div class="row">
<div class="col is-left h3">ID: {{ customer['id'] }}</div>
<div class="col is-center h3">Никнейм: {{ customer['username'] }}</div>
<div class="col is-right h3">Пароль: {{ customer['password'] }}</div>
</div>
<p class="row">Комментарии:</p>
<div class="row" v-if="!(customer['comments'].length == 0)">
<div class="col">
<div v-for="comment in customer['comments']" class="row is-left card mb-3">
<div class="row is-left h4">"{{ comment['content'] }}" - к посту '{{ comment['postTitle'] }}' от пользователя {{ comment['postAuthor'] }}</div>
</div>
</div>
</div>
<p v-else class="row">Нет комментариев</p>
<p class="row">Посты: </p>
<div class="row" v-if="!(customer['posts'].length == 0)">
<div class="col">
<div v-for="post in customer['posts']" class="row is-left card mb-3">
<div class="row is-center h1">{{ post['title'] }}</div>
<div class="row is-left h3">{{ post['content'] }}</div>
</div>
</div>
</div>
<p v-else class="row">Нет постов</p>
<div class="row" v-if="customer['id'] == currentCustomerId">
<button v-on:click="deleteUser(customer['id'])" class="col button dark outline">Удалить</button>
<button v-on:click="selectedCustomer = customer; usernameModal=customer['username'];passwordModal=customer['password']" class="col button primary outline" data-bs-toggle="modal" data-bs-target="#customerEdit">Редактировать</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerCreate" tabindex="-1" role="dialog" aria-labelledby="customerCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="customerCreateLabel">Создать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea v-model='usernameModal' id="usernameText" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea v-model='passwordModal' id="passwordText" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="button" class="btn btn-primary" v-on:click='createUser'>Сохранить</button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerEdit" tabindex="-1" role="dialog" aria-labelledby="customerEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="customerEditLabel">Редактировать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea v-model='usernameModal' id="usernameText" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea v-model='passwordModal' id="passwordText" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="button" class="btn btn-primary" v-on:click='editUser'>Изменить</button>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
customers: [],
usernameModal: '',
passwordModal: '',
selectedCustomer: {},
currentCustomerId: -1
}
},
methods: {
async createUser(){
let customer = {
username: this.usernameModal,
password: this.passwordModal
};
const response = await axios.post('http://localhost:8080/api/customer', customer);
this.refreshList();
},
async deleteUser(id) {
const response = await axios.delete('http://localhost:8080/api/customer/' + id);
this.refreshList();
},
async editUser() {
let customer = {
username: this.usernameModal,
password: this.passwordModal
};
const response = await axios.put('http://localhost:8080/api/customer/' + this.selectedCustomer['id'], customer);
this.refreshList();
},
async refreshList() {
this.customers = [];
if (this.$route.params.id === "") {
const response = await axios.get('http://localhost:8080/api/customer');
response.data.forEach(element => {
this.customers.push(element);
console.log(element);
});
} else {
const response = await axios.get('http://localhost:8080/api/customer/' + this.$route.params.id);
this.customers.push(response.data)
}
},
},
async mounted() {
this.currentCustomerId = history.state;
setInterval(async () => this.currentCustomerId = history.state, 50)
if (this.$route.params.id === "") {
const response = await axios.get('http://localhost:8080/api/customer');
response.data.forEach(element => {
this.customers.push(element);
console.log(element);
});
} else {
const response = await axios.get('http://localhost:8080/api/customer/' + this.$route.params.id);
this.customers.push(response.data)
}
},
async beforeRouteUpdate(to, from) {
this.$route.params.id = to.params.id;
this.refreshList();
},
}
</script>
<style lang="">
</style>

View File

@ -0,0 +1,247 @@
<template>
<div class="row">
<div class="col">
<div class="row mb-5">
<p class='is-center h2'>Посты</p>
</div>
<div class="row mb-5" v-if="currentCustomerId != -1">
<div class="col"></div>
<div class="col-10">
<div class="row mb-4 is-center">
<p class="col-2 is-left mb-2">Заголовок:</p>
<input type="text" class="col-6" v-model="titleModal"/>
</div>
<div class="row mb-4 is-center">
<p class="col-2 is-left mb-2">Текст:</p>
<textarea type="textarea" class="col-6" v-model="postContentModal"/>
</div>
<div class="row is-center">
<button type='button' class="button primary col-8" v-on:click='createPost'>
Опубликовать
</button>
</div>
</div>
<div class="col"></div>
</div>
<div v-if="!(posts.length == 0)" class="row">
<div class="col">
<div class="row mb-5 card" v-for="post in posts">
<div class="col">
<div class="row h3">
<div class="col is-left">
<p>Автор: <router-link v-bind:to="'/customers/' + post['customerId']" class="text-primary">{{ post['customerName'] }}</router-link></p>
</div>
<div class="col is-right">
<p>Дата: <span class="text-primary">{{ post['createDate'] }}</span></p>
</div>
</div>
<div class="row text-center">
<span class="h1">{{ post['title'] }}</span>
</div>
<div class="row text-center mb-5">
<span class="h3">{{ post['content'] }}</span>
</div>
<div class="row">
<div class="col">
<p class="row h2 is-center mb-3">Комментарии</p>
<div v-if="!(post['comments'].length == 0)" class="row text-left mb-5 card" v-for="comment in post['comments']">
<div class="row mb-1">
<div class="col is-left">
<span class="h2 text-primary">{{ comment['customerName'] }}</span>
</div>
<div class="col is-right">
<span class="h2 text-primary">{{ comment['createDate'] }}</span>
</div>
</div>
<div class="row mb-3">
<span class="h3">{{ comment['content'] }}</span>
</div>
<div v-if="selectedCustomerContainsComment(comment) == true" class="row">
<button v-on:click="selectedCommentId = comment['id']; selectedPostTitle = post['title']" class="button primary outline col" data-bs-toggle="modal" data-bs-target="#commentEdit">Изменить комментарий</button>
<button v-on:click="deleteComment(comment['id'])" class="button error col">Удалить комментарий</button>
</div>
</div>
<p v-else class="h3 row is-center mb-5">Пусто</p>
<div class="row" v-if="currentCustomerId != -1">
<input type="text" v-bind:id="'post-comment-' + post['id']" class="col-9"/>
<button type="button" v-on:click="selectedPostId = post['id']; selectedPostTitle = post['title']; createComment()" class="button col-3 secondary outline">Комментировать</button>
</div>
</div>
</div>
<div class="row" v-if="post['customerId'] == currentCustomerId">
<button type="button" v-on:click="deletePost(post['id'])" class="col button dark outline">Удалить пост</button>
<button type="button" v-on:click="selectedPostId = post['id']" class="col button primary outline" data-bs-toggle="modal" data-bs-target="#postEdit">Изменить пост</button>
</div>
</div>
</div>
</div>
</div>
<div v-else class="row text-center is-center">
Нет постов
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="commentEdit" tabindex="-1" role="dialog" aria-labelledby="commentEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commentEditLabel">Изменить комментарий</h5>
</div>
<div class="modal-body text-center">
<p>Под именем:</p>
<p>{{ currentCustomer['username'] }}</p>
<p>К посту:</p>
<p>{{ selectedPostTitle }}</p>
<p>Комментарий</p>
<textarea v-model='contentModal' id="editModalText" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="button" class="btn btn-primary" v-on:click='editComment'>Изменить</button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="postEdit" tabindex="-1" role="dialog" aria-labelledby="postEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postEditLabel">Редактировать пост</h5>
</div>
<div class="modal-body text-center">
<p>{{ currentCustomer['username'] }}</p>
<p>Заголовок</p>
<textarea v-model='titleModal' id="editModalTitle" cols="30" rows="1"></textarea>
<p>Содержание</p>
<textarea v-model='postContentModal' id="editModalPostContent" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="button" class="btn btn-primary" v-on:click='editPost'>Изменить</button>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
customers: [],
selectedCustomerId: 0,
currentCustomer: {},
posts: [],
selectedPostId: 0,
selectedPostTitle: '',
contentModal: '',
titleModal: '',
postContentModal: '',
selectedCommentId: 0,
currentCustomerId: -1,
}
},
methods: {
selectedCustomerContainsComment(comment) {
var customer = this.customers.find(element => element['id'] == this.currentCustomerId);
console.log(customer);
if (customer == null) return false;
var flag = false;
customer['comments'].forEach(commentIn => {
if (commentIn['id'] == comment['id']) {
flag = true;
}
});
return flag;
},
async createComment() {
const content = document.getElementById("post-comment-" + this.selectedPostId).value
let comment = {
"content": content,
customerId: this.currentCustomerId,
postId: this.selectedPostId
};
await axios.post('http://localhost:8080/api/comment', comment);
document.getElementById("post-comment-" + this.selectedPostId).value = ''
this.refreshList();
},
async deleteComment(commentId) {
await axios.delete('http://localhost:8080/api/comment/' + commentId);
this.refreshList();
},
async editComment(){
let comment = {
content: this.contentModal,
customerId: this.currentCustomerId,
postId: 0
};
await axios.put('http://localhost:8080/api/comment/' + this.selectedCommentId, comment);
this.refreshList();
},
async createPost() {
let post = {
"content": this.postContentModal,
title: this.titleModal,
customerId: this.currentCustomerId
};
const response = await axios.post('http://localhost:8080/api/post', post);
this.titleModal = ''
this.postContentModal = ''
this.refreshList();
},
async deletePost(postId) {
const response = await axios.delete('http://localhost:8080/api/post/' + postId);
this.refreshList();
},
async editPost(){
let post = {
"content": this.postContentModal,
title: this.titleModal,
customerId: this.currentCustomerId
};
const response = await axios.put('http://localhost:8080/api/post/' + this.selectedPostId, post);
this.refreshList();
},
async refreshList(){
this.customers = [];
this.posts = [];
const responseCustomer = await axios.get('http://localhost:8080/api/customer');
responseCustomer.data.forEach(element => {
this.customers.push(element);
console.log(element);
});
const responsePost = await axios.get('http://localhost:8080/api/post');
responsePost.data.forEach(element => {
this.posts.splice(0, 0, element);
console.log(element);
});
}
},
async beforeMount() {
this.currentCustomerId = history.state;
setInterval(async () => this.currentCustomerId = history.state, 50)
const responseCustomer = await axios.get('http://localhost:8080/api/customer');
responseCustomer.data.forEach(element => {
this.customers.push(element);
if (element['id'] == this.currentCustomerId) {
this.currentCustomer = element;
}
console.log(element);
});
const responsePost = await axios.get('http://localhost:8080/api/post');
responsePost.data.forEach(element => {
this.posts.splice(0, 0, element);
console.log(element);
});
}
}
</script>
<style lang="">
</style>

29
frontend/src/main.js Normal file
View File

@ -0,0 +1,29 @@
import {createApp} from 'vue'
import App from './App'
import { createRouter, createWebHistory } from "vue-router"
import Customers from './components/Customers'
import Posts from './components/Posts'
const routes = [
{
path: '/customers/:id?',
name: "Customers",
component: Customers,
props: true
},
{
path: '/posts',
name: "Posts",
component: Posts,
props: true
}
]
const router = createRouter({
routes,
history: createWebHistory()
})
createApp(App).use(router).mount('#app')

4
frontend/vue.config.js Normal file
View File

@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

View File

@ -0,0 +1,101 @@
package np.something.DTO;
import np.something.model.Comment;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.format.DateTimeFormatter;
public class CommentDto {
public long id;
public String content;
public long customerId;
public String customerName;
public long postId;
public String postTitle;
public String postAuthor;
public long postAuthorId;
public String createDate;
public CommentDto() {
}
public CommentDto(Comment comment) {
this.id = comment.getId();
this.content = comment.getContent();
this.customerId = comment.getCustomer().getId();
this.customerName = comment.getCustomer().getUsername();
this.postId = comment.getPost().getId();
this.postTitle = comment.getPost().getTitle();
this.postAuthor = comment.getPost().getCustomer().getUsername();
this.postAuthorId = comment.getPost().getCustomer().getId();
this.createDate = comment.getCreateDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getCustomerId() {
return customerId;
}
public void setCustomerId(long customerId) {
this.customerId = customerId;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getCustomerName() {
return customerName;
}
public long getPostId() {
return postId;
}
public void setPostId(long postId) {
this.postId = postId;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getPostTitle() {return postTitle;}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getPostAuthor() {
return postAuthor;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getPostAuthorId() {
return postAuthorId;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getCreateDate() {
return createDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommentDto that = (CommentDto) o;
return id == that.id;
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
}

View File

@ -0,0 +1,58 @@
package np.something.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import np.something.model.Customer;
import java.io.Serializable;
import java.util.List;
public class CustomerDto {
public long id;
public String username;
public String password;
public List<CommentDto> comments;
public List<PostDto> posts;
public CustomerDto() {
}
public CustomerDto(Customer customer) {
this.id = customer.getId();
this.username = customer.getUsername();
this.password = customer.getPassword();
this.comments = customer.getComments().stream().map(CommentDto::new).toList();
this.posts = customer.getPosts().stream().map(PostDto::new).toList();
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<CommentDto> getComments() {
return comments;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<PostDto> getPosts() {
return posts;
}
}

View File

@ -0,0 +1,77 @@
package np.something.DTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import np.something.model.Post;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class PostDto {
public long id;
public String title;
public String content;
public String customerName;
public long customerId;
public ArrayList<CommentDto> comments;
public String createDate;
public PostDto() {
}
public PostDto(Post post) {
this.id = post.getId();
this.title = post.getTitle();
this.content = post.getContent();
this.customerName = post.getCustomer().getUsername();
this.customerId = post.getCustomer().getId();
this.comments = new ArrayList<>(post.getComments().stream().map(CommentDto::new).toList());
this.createDate = post.getCreateDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getCustomerName() {
return customerName;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<CommentDto> getComments() {
return comments;
}
public long getCustomerId() {
return customerId;
}
public void setCustomerId(long customerId) {
this.customerId = customerId;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getCreateDate() {
return createDate;
}
}

View File

@ -0,0 +1,7 @@
package np.something.Exceptions;
public class CommentNotFoundException extends RuntimeException {
public CommentNotFoundException(Long id) {
super(String.format("Comment with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,7 @@
package np.something.Exceptions;
public class CustomerNotFoundException extends RuntimeException {
public CustomerNotFoundException(Long id) {
super(String.format("Customer with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,7 @@
package np.something.Exceptions;
public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(Long id) {
super(String.format("Post with id [%s] is not found", id));
}
}

View File

@ -6,6 +6,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
public static final String REST_API = "/api";
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");

View File

@ -0,0 +1,61 @@
package np.something.controllers;
import jakarta.validation.Valid;
import np.something.DTO.CommentDto;
import np.something.WebConfiguration;
import np.something.model.Comment;
import np.something.services.*;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/comment")
public class CommentController {
private final CommentService commentService;
private final CustomerService customerService;
private final PostService postService;
public CommentController(CommentService commentService, CustomerService customerService, PostService postService) {
this.commentService = commentService;
this.customerService = customerService;
this.postService = postService;
}
@GetMapping("/{id}")
public CommentDto getComment(@PathVariable Long id) {
return new CommentDto(commentService.findComment(id));
}
@GetMapping
public List<CommentDto> getComments() {
return commentService.findAllComments().stream()
.map(CommentDto::new)
.toList();
}
@PostMapping
public CommentDto createComment(@RequestBody @Valid CommentDto commentDto){
final Comment comment = commentService.addComment(
customerService.findCustomer(commentDto.getCustomerId()),
postService.findPost(commentDto.getPostId()),
commentDto.getContent()
);
return new CommentDto(comment);
}
@PutMapping("/{id}")
public CommentDto updateComment(@RequestBody @Valid CommentDto commentDto, @PathVariable Long id) {
return new CommentDto(commentService.updateComment(id, commentDto.getContent()));
}
@DeleteMapping("/{id}")
public CommentDto deleteComment(@PathVariable Long id) {
return new CommentDto(commentService.deleteComment(id));
}
@DeleteMapping
public void deleteAllComments(){
commentService.deleteAllComments();
}
}

View File

@ -0,0 +1,53 @@
package np.something.controllers;
import jakarta.validation.Valid;
import np.something.DTO.CustomerDto;
import np.something.WebConfiguration;
import np.something.model.Customer;
import np.something.services.*;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/customer")
public class CustomerController {
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
@GetMapping("/{id}")
public CustomerDto getCustomer(@PathVariable Long id) {
return new CustomerDto(customerService.findCustomer(id));
}
@GetMapping
public List<CustomerDto> getCustomers() {
return customerService.findAllCustomers().stream()
.map(CustomerDto::new)
.toList();
}
@PostMapping
public CustomerDto createCustomer(@RequestBody @Valid CustomerDto customerDto){
final Customer customer = customerService.addCustomer(customerDto.getUsername(), customerDto.getPassword());
return new CustomerDto(customer);
}
@PutMapping("/{id}")
public CustomerDto updateCustomer(@RequestBody @Valid CustomerDto customerDto, @PathVariable Long id) {
return new CustomerDto(customerService.updateCustomer(id, customerDto.getUsername(), customerDto.getPassword()));
}
@DeleteMapping("/{id}")
public CustomerDto deleteCustomer(@PathVariable Long id) {
return new CustomerDto(customerService.deleteCustomer(id));
}
@DeleteMapping
public void deleteAllCustomers(){
customerService.deleteAllCustomers();
}
}

View File

@ -1,41 +0,0 @@
package np.something.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MathController {
@GetMapping("/sum")
public int sum(
@RequestParam(value = "num1", defaultValue = "0") int num1,
@RequestParam(value = "num2", defaultValue = "0") int num2
) {
return num1 + num2;
}
@GetMapping("/sub")
public int sub(
@RequestParam(value = "num1", defaultValue = "0") int num1,
@RequestParam(value = "num2", defaultValue = "0") int num2
) {
return num1 - num2;
}
@GetMapping("/mul")
public int mul(
@RequestParam(value = "num1", defaultValue = "0") int num1,
@RequestParam(value = "num2", defaultValue = "0") int num2
) {
return num1 * num2;
}
@GetMapping("/div")
public int div(
@RequestParam(value = "num1", defaultValue = "0") int num1,
@RequestParam(value = "num2", defaultValue = "0") int num2
) {
return num1 / num2;
}
}

View File

@ -0,0 +1,55 @@
package np.something.controllers;
import jakarta.validation.Valid;
import np.something.DTO.PostDto;
import np.something.WebConfiguration;
import np.something.services.CustomerService;
import np.something.services.PostService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/post")
public class PostController {
private final PostService postService;
private final CustomerService customerService;
public PostController(PostService postService, CustomerService customerService) {
this.postService = postService;
this.customerService = customerService;
}
@GetMapping("/{id}")
public PostDto getPost(@PathVariable Long id) {
return new PostDto(postService.findPost(id));
}
@GetMapping
public List<PostDto> getPosts() {
return postService.findAllPosts().stream()
.map(PostDto::new)
.toList();
}
@PostMapping
public PostDto createPost(@RequestBody @Valid PostDto postDto)
{
return new PostDto(postService.addPost(customerService.findCustomer(postDto.getCustomerId()), postDto.getTitle(), postDto.getContent()));
}
@PutMapping("/{id}")
public PostDto updatePost(@RequestBody @Valid PostDto postDto, @PathVariable Long id)
{
return new PostDto(postService.updatePost(id, postDto.title, postDto.content));
}
@DeleteMapping("/{id}")
public PostDto deletePost (@PathVariable Long id) {
return new PostDto(postService.deletePost(id));
}
@DeleteMapping
public void deleteAllPosts() {
postService.deleteAllPosts();
}
}

View File

@ -0,0 +1,82 @@
package np.something.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
@NotBlank(message = "Content cannot be empty")
private String content;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="customer_fk")
private Customer customer;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="post_fk")
private Post post;
@Column(columnDefinition = "timestamp default NOW()")
private LocalDateTime createDate;
public Comment() {
}
public Comment(Customer customer, Post post, String content) {
this.customer = customer;
this.post = post;
this.content = content;
}
public Long getId() {
return id;
}
public Post getPost() {
return post;
}
public Customer getCustomer() {
return customer;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Comment comment = (Comment) o;
return Objects.equals(id, comment.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public LocalDateTime getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
}
}

View File

@ -0,0 +1,75 @@
package np.something.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.util.*;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
@NotBlank(message = "Username cannot be empty")
private String username;
@Column
@NotBlank(message = "Password cannot be empty")
private String password;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "customer", cascade = CascadeType.ALL)
private List<Comment> comments;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "customer", cascade = CascadeType.ALL)
private List<Post> posts;
public Customer() {
}
public Customer(String username, String password) {
this.username = username;
this.password = password;
this.comments = new ArrayList<>();
this.posts = new ArrayList<>();
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public List<Comment> getComments() {
return comments;
}
public List<Post> getPosts() {
return posts;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Customer customer = (Customer) obj;
return Objects.equals(id, customer.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@ -0,0 +1,95 @@
package np.something.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
@NotBlank(message = "Title cannot be empty")
private String title;
@Column
@NotBlank(message = "Content cannot be empty")
private String content;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "customer_fk")
private Customer customer;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "post", cascade = CascadeType.ALL)
private List<Comment> comments;
@Column(columnDefinition = "timestamp default NOW()")
private LocalDateTime createDate;
public Post() {
}
public Post(Customer customer, String title, String content) {
this.customer = customer;
this.title = title;
this.content = content;
this.comments = new ArrayList<>();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Post post = (Post) o;
return id.equals(post.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<Comment> getComments() {
return comments;
}
public Customer getCustomer() {
return customer;
}
public Long getId() {
return id;
}
public LocalDateTime getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
}
}

View File

@ -0,0 +1,61 @@
package np.something.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import np.something.DTO.CustomerDto;
import np.something.services.CustomerService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/admin")
public class Admin {
private final CustomerService customerService;
public Admin(CustomerService customerService) {
this.customerService = customerService;
}
@GetMapping(value = { "/", "/{id}" })
public String getCustomers(@PathVariable(required = false) Long id, HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
if (id == null || id <= 0) {
model.addAttribute("customers", customerService.findAllCustomers().stream().map(CustomerDto::new).toList());
return "admin";
} else {
return "redirect:/customers/" + id;
}
}
@PostMapping("/delete/{id}")
public String deleteCustomer(@PathVariable Long id, HttpSession session) {
session.setAttribute("currentCustomerId", -1);
customerService.deleteCustomer(id);
return "redirect:/admin/";
}
@PostMapping(value = { "/", "/{id}"})
public String manipulateCustomer(@PathVariable(required = false) Long id, @ModelAttribute @Valid CustomerDto customerDto,
HttpServletRequest request, HttpSession session,
BindingResult bindingResult,
Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "/admin";
}
if (id == null || id <= 0) {
customerService.addCustomer(customerDto.username, customerDto.password);
} else {
customerService.updateCustomer(id, customerDto.username, customerDto.password);
}
return "redirect:/admin/";
}
}

View File

@ -0,0 +1,55 @@
package np.something.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import np.something.DTO.CommentDto;
import np.something.DTO.PostDto;
import np.something.services.CommentService;
import np.something.services.CustomerService;
import np.something.services.PostService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/comments")
public class Comments {
private final CustomerService customerService;
private final CommentService commentService;
private final PostService postService;
public Comments(CustomerService customerService, CommentService commentService, PostService postService) {
this.customerService = customerService;
this.commentService = commentService;
this.postService = postService;
}
@PostMapping(value = { "/", "/{id}"})
public String manipulateComment(@PathVariable(required = false) Long id, @ModelAttribute @Valid CommentDto commentDto,
HttpServletRequest request, HttpSession session, BindingResult bindingResult, Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
model.addAttribute("posts", postService.findAllPosts().stream().map(PostDto::new).toList());
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "/feed";
}
if (id == null || id <= 0) {
commentService.addComment(customerService.findCustomer(commentDto.customerId), postService.findPost(commentDto.postId), commentDto.content);
} else {
commentService.updateComment(id, commentDto.content);
}
return "redirect:/feed";
}
@PostMapping("/delete/{id}")
public String deleteComment(@PathVariable Long id) {
commentService.deleteComment(id);
return "redirect:/feed";
}
}

View File

@ -0,0 +1,62 @@
package np.something.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import np.something.DTO.CustomerDto;
import np.something.services.CustomerService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/customers")
public class Customers {
private final CustomerService customerService;
public Customers(CustomerService customerService) {
this.customerService = customerService;
}
@GetMapping(value = { "/", "/{id}" })
public String getCustomers(@PathVariable(required = false) Long id, HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
if (id == null || id <= 0) {
model.addAttribute("customers", customerService.findAllCustomers().stream().map(CustomerDto::new).toList());
} else {
model.addAttribute("customers", new CustomerDto[] { new CustomerDto(customerService.findCustomer(id)) });
}
return "customers";
}
@PostMapping("/delete/{id}")
public String deleteCustomer(@PathVariable Long id, HttpSession session) {
session.setAttribute("currentCustomerId", -1);
customerService.deleteCustomer(id);
return "redirect:/customers/";
}
@PostMapping(value = { "/", "/{id}"})
public String manipulateCustomer(@PathVariable(required = false) Long id, @ModelAttribute @Valid CustomerDto customerDto,
HttpServletRequest request, HttpSession session,
BindingResult bindingResult,
Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "/customers";
}
if (id == null || id <= 0) {
customerService.addCustomer(customerDto.username, customerDto.password);
} else {
customerService.updateCustomer(id, customerDto.username, customerDto.password);
}
return "redirect:/customers/";
}
}

View File

@ -0,0 +1,71 @@
package np.something.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import np.something.DTO.CommentDto;
import np.something.DTO.CustomerDto;
import np.something.DTO.PostDto;
import np.something.model.Post;
import np.something.services.CommentService;
import np.something.services.CustomerService;
import np.something.services.PostService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Controller
@RequestMapping("/feed")
public class Feed {
private final PostService postService;
private final CustomerService customerService;
private final CommentService commentService;
public Feed(PostService postService, CustomerService customerService, CommentService commentService) {
this.postService = postService;
this.customerService = customerService;
this.commentService = commentService;
}
@GetMapping
public String getPosts(@RequestParam(required = false) String search, HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
if (search == null) {
model.addAttribute("posts", postService.findAllPosts().stream().map(PostDto::new).toList());
} else {
var posts = new ArrayList<>(postService.searchPosts(search));
var comments = commentService.searchComments(search);
for (var post: posts) {
post.getComments().clear();
}
for (var comment: comments) {
boolean found = false;
for (var post: posts) {
if (Objects.equals(comment.getPost().getId(), post.getId())) {
post.getComments().add(comment);
found = true;
break;
}
}
if (!found) {
var newPost = comment.getPost();
newPost.getComments().clear();
newPost.getComments().add(comment);
posts.add(newPost);
}
}
model.addAttribute("posts", posts.stream().map(PostDto::new).toList());
}
model.addAttribute("customers", customerService.findAllCustomers().stream().map(CustomerDto::new).toList());
return "/feed";
}
}

View File

@ -0,0 +1,56 @@
package np.something.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import np.something.services.CommentService;
import np.something.services.CustomerService;
import np.something.services.PostService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;
import jakarta.validation.Valid;
import np.something.DTO.PostDto;
@Controller
@RequestMapping("/posts")
public class Posts {
private final CustomerService customerService;
private final CommentService commentService;
private final PostService postService;
public Posts(CustomerService customerService, CommentService commentService, PostService postService) {
this.customerService = customerService;
this.commentService = commentService;
this.postService = postService;
}
@PostMapping("/delete/{id}")
public String deletePost(@PathVariable Long id) {
postService.deletePost(id);
return "redirect:/feed";
}
@PostMapping(value = { "/", "/{id}"})
public String manipulatePost(@PathVariable(required = false) Long id, @ModelAttribute @Valid PostDto postDto,
HttpServletRequest request, HttpSession session,
BindingResult bindingResult,
Model model) {
model.addAttribute("request", request);
model.addAttribute("session", session);
model.addAttribute("posts", postService.findAllPosts().stream().map(PostDto::new).toList());
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "/feed";
}
if (id == null || id <= 0) {
postService.addPost(customerService.findCustomer(postDto.customerId), postDto.title, postDto.content);
} else {
postService.updatePost(id, postDto.title, postDto.content);
}
return "redirect:/feed";
}
}

View File

@ -0,0 +1,16 @@
package np.something.mvc;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class Session {
@PostMapping("/update-session")
public ResponseEntity<Void> updateSession(@RequestParam("currentCustomerId") int currentCustomerId, HttpSession session) {
session.setAttribute("currentCustomerId", currentCustomerId);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,13 @@
package np.something.repositories;
import np.something.model.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface CommentRepository extends JpaRepository<Comment, Long> {
@Query("SELECT DISTINCT c FROM Comment c WHERE c.content LIKE %:tag%")
List<Comment> searchComments(@Param("tag") String tag);
}

View File

@ -0,0 +1,7 @@
package np.something.repositories;
import np.something.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

View File

@ -0,0 +1,14 @@
package np.something.repositories;
import np.something.model.Comment;
import np.something.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT DISTINCT p FROM Post p WHERE p.title LIKE %:tag% OR p.content LIKE %:tag%")
List<Post> searchPosts(@Param("tag") String tag);
}

View File

@ -0,0 +1,74 @@
package np.something.services;
import np.something.Exceptions.CommentNotFoundException;
import org.springframework.transaction.annotation.Transactional;
import np.something.model.Comment;
import np.something.model.Customer;
import np.something.model.Post;
import np.something.repositories.CommentRepository;
import np.something.util.validation.ValidatorUtil;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
public class CommentService {
private final CommentRepository commentRepository;
private final ValidatorUtil validatorUtil;
public CommentService(CommentRepository commentRepository,
ValidatorUtil validatorUtil) {
this.commentRepository = commentRepository;
this.validatorUtil = validatorUtil;
}
@Transactional(readOnly = true)
public Comment findComment(Long id) {
final Optional<Comment> comment = commentRepository.findById(id);
return comment.orElseThrow(() -> new CommentNotFoundException(id));
}
@Transactional(readOnly = true)
public List<Comment> findAllComments() {
return commentRepository.findAll();
}
@Transactional
public Comment addComment(Customer customer, Post post, String content) {
final Comment comment = new Comment(customer, post, content);
comment.setCreateDate(LocalDateTime.now());
validatorUtil.validate(comment);
customer.getComments().add(comment);
post.getComments().add(comment);
return commentRepository.save(comment);
}
@Transactional
public Comment updateComment(Long id, String content) {
final Comment currentComment = findComment(id);
currentComment.setContent(content);
validatorUtil.validate(currentComment);
return commentRepository.save(currentComment);
}
@Transactional
public Comment deleteComment(Long id) {
final Comment currentComment = findComment(id);
commentRepository.delete(currentComment);
currentComment.getPost().getComments().remove(currentComment);
currentComment.getCustomer().getComments().remove(currentComment);
return currentComment;
}
@Transactional
public void deleteAllComments() {
commentRepository.deleteAll();
}
@jakarta.transaction.Transactional
public List<Comment> searchComments(String tag) {
return commentRepository.searchComments(tag);
}
}

View File

@ -0,0 +1,65 @@
package np.something.services;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import np.something.Exceptions.CustomerNotFoundException;
import np.something.model.Customer;
import np.something.repositories.CommentRepository;
import np.something.repositories.CustomerRepository;
import np.something.util.validation.ValidatorUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
private final ValidatorUtil validatorUtil;
public CustomerService(CustomerRepository customerRepository,
ValidatorUtil validatorUtil) {
this.customerRepository = customerRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public Customer findCustomer(Long id) {
return customerRepository.findById(id).orElseThrow(() -> new CustomerNotFoundException(id));
}
@Transactional
public List<Customer> findAllCustomers() {
return customerRepository.findAll();
}
@Transactional
public Customer addCustomer(String username, String password) {
Customer customer = new Customer(username, password);
validatorUtil.validate(customer);
return customerRepository.save(customer);
}
@Transactional
public Customer updateCustomer(Long id, String username, String password) {
Customer customer = findCustomer(id);
customer.setUsername(username);
customer.setPassword(password);
validatorUtil.validate(customer);
return customerRepository.save(customer);
}
@Transactional
public Customer deleteCustomer(Long id) {
Customer customer = findCustomer(id);
customerRepository.delete(customer);
return customer;
}
@Transactional
public void deleteAllCustomers() {
customerRepository.deleteAll();
}
}

View File

@ -0,0 +1,76 @@
package np.something.services;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import np.something.Exceptions.PostNotFoundException;
import np.something.model.Comment;
import np.something.model.Customer;
import np.something.model.Post;
import np.something.repositories.CustomerRepository;
import np.something.repositories.PostRepository;
import np.something.util.validation.ValidatorUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class PostService {
private final PostRepository postRepository;
private final ValidatorUtil validatorUtil;
public PostService(PostRepository postRepository,
ValidatorUtil validatorUtil) {
this.postRepository = postRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public Post findPost(Long id) {
return postRepository.findById(id).orElseThrow(() -> new PostNotFoundException(id));
}
@Transactional
public List<Post> findAllPosts() {
return postRepository.findAll();
}
@Transactional
public Post addPost(Customer customer, String title, String content) {
Post post = new Post(customer, title, content);
post.setCreateDate(LocalDateTime.now());
validatorUtil.validate(post);
customer.getPosts().add(post);
return postRepository.save(post);
}
@Transactional
public Post updatePost(Long id, String title, String content) {
Post post = findPost(id);
post.setTitle(title);
post.setContent(content);
validatorUtil.validate(post);
return postRepository.save(post);
}
@Transactional
public Post deletePost(Long id) {
Post post = findPost(id);
post.getCustomer().getPosts().remove(post);
postRepository.delete(post);
return post;
}
@Transactional
public void deleteAllPosts() {
postRepository.deleteAll();
}
@Transactional
public List<Post> searchPosts(String tag) {
return postRepository.searchPosts(tag);
}
}

View File

@ -0,0 +1,43 @@
package np.something.util.error;
import np.something.Exceptions.CommentNotFoundException;
import np.something.Exceptions.CustomerNotFoundException;
import np.something.Exceptions.PostNotFoundException;
import np.something.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.util.stream.Collectors;
@ControllerAdvice(annotations = RestController.class)
public class AdviceController {
@ExceptionHandler({
CommentNotFoundException.class,
CustomerNotFoundException.class,
PostNotFoundException.class,
ValidationException.class
})
public ResponseEntity<Object> handleException(Throwable e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
@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);
}
}

View File

@ -0,0 +1,9 @@
package np.something.util.validation;
import java.util.Set;
public class ValidationException extends RuntimeException {
public ValidationException(Set<String> errors) {
super(String.join("\n", errors));
}
}

View File

@ -0,0 +1,30 @@
package np.something.util.validation;
import org.springframework.stereotype.Component;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class ValidatorUtil {
private final Validator validator;
public ValidatorUtil() {
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
this.validator = factory.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()));
}
}
}

View File

@ -1 +1,11 @@
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

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<div class="col">
<div class="row mb-5">
<p class='is-center h2'>Профили</p>
</div>
<div class="row mb-5">
<div class="col"></div>
<div class="col-10 is-center">
<button class="button primary" data-bs-toggle="modal" data-bs-target="#customerCreate">
Добавить нового пользователя
</button>
</div>
<div class="col"></div>
</div>
<p class='h3 is-center row mb-5'>Список профилей</p>
<div class="row">
<div class="col">
<div class="row card mb-3">
<div class="row">
<div class="col-3 is-left h3 fw-bold" th:text="ID"></div>
<div class="col-3 is-center h3 fw-bold" th:text="Никнейм"></div>
<div class="col-3 is-right h3 fw-bold" th:text="Пароль"></div>
<div class="col-2"></div>
<div class="col-1"></div>
</div>
</div>
<div th:each="customer: ${customers}" class="row card mb-3">
<div class="row">
<div class="col-3 is-left h3" th:text="${customer.id}"></div>
<a th:href="${ '/customers/' + customer.id}" class="col-3 is-center h3" th:text="${customer.username}"></a>
<div class="col-3 is-right h3" th:text="${customer.password}"></div>
<button style="max-width: 66px; max-height: 38px;" th:data-username="${customer.username}" th:data-password="${customer.password}" th:data-id="${customer.id}" th:onclick="|prepareEditModal(this)|" class="button primary outline is-right" data-bs-toggle="modal" data-bs-target="#customerEdit"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<form th:action="@{/admin/delete/{id}(id=${customer.id})}" method="post" class="col-1 is-right">
<button class="button dark outline is-right" style="max-width: 66px; max-height: 38px;" type="submit"><i class="fa fa-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerCreate" tabindex="-1" role="dialog" aria-labelledby="customerCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" th:action="@{/admin/}" method="post">
<div class="modal-header">
<h5 class="modal-title" id="customerCreateLabel">Создать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea name="username" id="usernameTextC" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea name="password" id="passwordTextC" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerEdit" tabindex="-1" role="dialog" aria-labelledby="customerEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" id="edit-customer-form" method="post">
<div class="modal-header">
<h5 class="modal-title" id="customerEditLabel">Редактировать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea name="username" id="usernameTextE" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea name="password" id="passwordTextE" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Изменить</button>
</div>
</form>
</div>
</div>
</div>
</body>
<th:block layout:fragment="scripts">
<script th:inline="javascript">
function prepareEditModal(btn) {
document.getElementById('usernameTextE').value = btn.getAttribute("data-username");
document.getElementById('passwordTextE').value = btn.getAttribute("data-password");
document.getElementById('edit-customer-form').setAttribute('action', '/admin/' + btn.getAttribute("data-id"));
}
</script>
</th:block>
</html>

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<div class="col">
<div class="row mb-5">
<p class='is-center h2'>Профили</p>
</div>
<div class="row mb-5">
<div class="col"></div>
<div class="col-10 is-center">
<button class="button primary" data-bs-toggle="modal" data-bs-target="#customerCreate">
Добавить нового пользователя
</button>
</div>
<div class="col"></div>
</div>
<p class='h3 is-center row mb-5'>Список профилей</p>
<div class="row">
<div class="col">
<div th:each="customer: ${customers}" class="row card mb-3">
<div class="row">
<div class="col is-left h3" th:text="'ID: ' + ${customer.id}"></div>
<div class="col is-center h3" th:text="'Никнейм: ' + ${customer.username}"></div>
<div class="col is-right h3" th:text="'Пароль: ' + ${customer.password}"></div>
</div>
<p class="row">Комментарии:</p>
<div class="row" th:unless="${#arrays.isEmpty(customer.comments)}">
<div class="col">
<div th:each="comment: ${customer.comments}" class="row is-left card mb-3">
<div class="row is-left h4" th:text="${'&quot;' + comment.content + '&quot;' + ' - к посту ' + '&quot;' + comment.postTitle + '&quot;' + ' от пользователя ' + comment.postAuthor}"></div>
</div>
</div>
</div>
<p th:if="${#arrays.isEmpty(customer.comments)}" class="row">Нет комментариев</p>
<p class="row">Посты: </p>
<div class="row" th:unless="${#arrays.isEmpty(customer.comments)}">
<div class="col">
<div th:each="post: ${customer.posts}" class="row is-left card mb-3">
<div class="row is-center h1" th:text="${post.title}"></div>
<div class="row is-left h3" th:text="${post.content}"></div>
</div>
</div>
</div>
<p th:if="${#arrays.isEmpty(customer.comments)}" class="row">Нет постов</p>
<div class="row" th:if="${session.currentCustomerId == customer.id}">
<form th:action="@{/customers/delete/{id}(id=${customer.id})}" method="post" class="col">
<button class="button dark outline is-full-width" type="submit">Удалить</button>
</form>
<button th:data-username="${customer.username}" th:data-password="${customer.password}" th:data-id="${customer.id}" th:onclick="|prepareEditModal(this)|" class="col button primary outline" data-bs-toggle="modal" data-bs-target="#customerEdit">Редактировать</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerCreate" tabindex="-1" role="dialog" aria-labelledby="customerCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" th:action="@{/customers/}" method="post">
<div class="modal-header">
<h5 class="modal-title" id="customerCreateLabel">Создать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea name="username" id="usernameTextC" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea name="password" id="passwordTextC" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="customerEdit" tabindex="-1" role="dialog" aria-labelledby="customerEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" id="edit-customer-form" method="post">
<div class="modal-header">
<h5 class="modal-title" id="customerEditLabel">Редактировать профиль</h5>
</div>
<div class="modal-body text-center">
<p>Логин</p>
<textarea name="username" id="usernameTextE" cols="30" rows="1"></textarea>
<p>Пароль</p>
<textarea name="password" id="passwordTextE" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Изменить</button>
</div>
</form>
</div>
</div>
</div>
</body>
<th:block layout:fragment="scripts">
<script th:inline="javascript">
function prepareEditModal(btn) {
document.getElementById('usernameTextE').value = btn.getAttribute("data-username");
document.getElementById('passwordTextE').value = btn.getAttribute("data-password");
document.getElementById('edit-customer-form').setAttribute('action', '/customers/' + btn.getAttribute("data-id"));
}
</script>
</th:block>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/chota@latest">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<title>Социальная сеть</title>
</head>
<body class="container">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<div>
<div class="nav row">
<div class="nav-left col-10" th:with="activeLink=${request.requestURI}">
<a href="/customers/" class="button primary" th:classappend="${#strings.startsWith(activeLink, '/customers')} ? 'clear' : ''">Профили</a>
<a href="/feed" class="button primary" th:classappend="${#strings.startsWith(activeLink, '/feed')} ? 'clear' : ''">Посты</a>
</div>
<div class="nav-right col-2">
<select class="form-select" style="font-size: 16px;" th:onchange="updateSession()" id="selectBox">
<option value="-1" th:selected="${session.currentCustomerId == -1}" class="button dark outline">Не выбран</option>
<option th:each="customer: ${customers}" th:selected="${session.currentCustomerId == customer.id}" th:value="${customer.id}" class="button dark outline" th:text="${customer.username}"></option>
</select>
</div>
</div>
<div layout:fragment="content">
</div>
</div>
</body>
<script>
function updateSession() {
let selectBox = document.getElementById("selectBox");
let id = selectBox.options[selectBox.selectedIndex].value;
fetch("/update-session?currentCustomerId=" + id, {method: "post"})
location.reload();
}
</script>
<th:block layout:fragment="scripts">
</th:block>
</html>

View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<div class="col">
<div class="row mb-5">
<p class='is-center h2'>Посты</p>
</div>
<form class="row mb-5" action="/feed">
<input type="search" name="search" class="col-10">
<button class="button primary col" type="submit">Найти</button>
</form>
<div class="row mb-5" th:if="${session.currentCustomerId > 0}">
<div class="col"></div>
<form class="col-10" th:action="@{/posts/}" method="post">
<input name="customerId" type="text" style="display: none;" th:value="${session.currentCustomerId}">
<div class="row mb-4 is-center">
<p class="col-2 is-left mb-2">Заголовок:</p>
<input name="title" type="text" class="col-6" id="createPostTitle" />
</div>
<div class="row mb-4 is-center">
<p class="col-2 is-left mb-2">Текст:</p>
<textarea name="content" type="textarea" class="col-6" id="createPostContent"></textarea>
</div>
<div class="row is-center">
<button type='submit' class="button primary col-8">
Опубликовать
</button>
</div>
</form>
<div class="col"></div>
</div>
<div th:unless="${#arrays.isEmpty(posts)}" class="row">
<div class="col">
<div class="row mb-5 card" th:each="post: ${posts}">
<div class="col">
<div class="row h3">
<div class="col is-left">
<p>Автор: <a th:href="${'/customers/' + post.customerId}" class="text-primary" th:text="${post.customerName}"></a></p>
</div>
<div class="col is-right">
<p>Дата: <span class="text-primary" th:text="${post.createDate}"></span></p>
</div>
</div>
<div class="row text-center">
<span class="h1" th:text="${post.title}"></span>
</div>
<div class="row text-center mb-5">
<span class="h3" th:text="${post.content}"></span>
</div>
<div class="row">
<div class="col">
<p class="row h2 is-center mb-3">Комментарии</p>
<div th:unless="${#arrays.isEmpty(post.comments)}" class="row text-left mb-5 card" th:each="comment: ${post.comments}">
<div class="row mb-1">
<div class="col is-left">
<span class="h2 text-primary" th:text="${comment.customerName}"></span>
</div>
<div class="col is-right">
<span class="h2 text-primary" th:text="${comment.createDate}"></span>
</div>
</div>
<div class="row mb-3">
<span class="h3" th:text="${comment.content}"></span>
</div>
<div th:if="${session.currentCustomerId == comment.customerId}" class="row">
<button th:data-content="${comment.content}" th:data-id="${comment.id}" th:onclick="|prepareCommentEditModal(this)|" class="button primary outline col" data-bs-toggle="modal" data-bs-target="#commentEdit">Изменить комментарий</button>
<form th:action="@{/comments/delete/{id}(id=${comment.id})}" method="post" class="col">
<button type="submit" class="button error is-full-width">Удалить комментарий</button>
</form>
</div>
</div>
<p th:if="${#arrays.isEmpty(post.comments)}" class="h3 row is-center mb-5">Пусто</p>
<form class="row" th:if="${session.currentCustomerId != -1}" th:action="@{/comments/}" method="post">
<input name="content" type="text" class="col-9"/>
<input name="customerId" type="text" style="display: none;" th:value="${session.currentCustomerId}">
<input name="postId" type="text" style="display: none;" th:value="${post.id}">
<button type="submit" class="button col-3 secondary outline">Комментировать</button>
</form>
</div>
</div>
<div class="row" th:if="${session.currentCustomerId == post.customerId}">
<form class="col" th:action="@{/posts/delete/{id}(id=${post.id})}" method="post">
<button type="submit" class="is-full-width button dark outline">Удалить пост</button>
</form>
<button th:data-customer="${session.currentCustomerId}" th:data-id="${post.id}" th:data-title="${post.title}" th:data-content="${post.content}" type="button" th:onclick="|preparePostEditModal(this)|" class="col button primary outline" data-bs-toggle="modal" data-bs-target="#postEdit">Изменить пост</button>
</div>
</div>
</div>
</div>
</div>
<div th:if="${#arrays.isEmpty(posts)}" class="row text-center is-center">
Нет постов
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="commentEdit" tabindex="-1" role="dialog" aria-labelledby="commentEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form method="post" class="modal-content" id="editComment">
<div class="modal-header">
<h5 class="modal-title" id="commentEditLabel">Изменить комментарий</h5>
</div>
<div class="modal-body text-center">
<textarea name='content' id="editModalText" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Изменить</button>
</div>
</form>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="postEdit" tabindex="-1" role="dialog" aria-labelledby="postEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" id="editPost">
<div class="modal-header">
<h5 class="modal-title" id="postEditLabel">Редактировать пост</h5>
</div>
<div class="modal-body text-center">
<p>Заголовок</p>
<textarea name="title" id="editModalTitle" cols="30" rows="1"></textarea>
<p>Содержание</p>
<textarea name="content" id="editModalPostContent" cols="30" rows="1"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary">Изменить</button>
</div>
</form>
</div>
</div>
</div>
</body>
<th:block layout:fragment="scripts">
<script th:inline="javascript">
function prepareCommentEditModal(btn) {
document.getElementById('editModalText').value = btn.getAttribute("data-content");
document.getElementById('editComment').setAttribute('action', '/comments/' + btn.getAttribute("data-id"));
}
function preparePostEditModal(btn) {
document.getElementById('editModalTitle').value = btn.getAttribute("data-title");
document.getElementById('editModalPostContent').value = btn.getAttribute("data-content");
document.getElementById('editPost').setAttribute('action', '/posts/' + btn.getAttribute("data-id"));
}
</script>
</th:block>
</html>

View File

@ -0,0 +1,123 @@
package np.something;
import np.something.model.*;
import np.something.services.CommentService;
import np.something.services.CustomerService;
import np.something.services.PostService;
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 SocialNetworkTest {
@Autowired
CustomerService customerService;
@Autowired
CommentService commentService;
@Autowired
PostService postService;
@Test
void testCustomers() {
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
Customer c1 = customerService.addCustomer("first", "1");
Customer c2 = customerService.addCustomer("second", "2");
Customer c3 = customerService.addCustomer("third", "3");
Assertions.assertEquals("first", c1.getUsername());
Assertions.assertEquals("second", c2.getUsername());
Assertions.assertEquals("third", c3.getUsername());
Assertions.assertEquals(c1, customerService.findCustomer(c1.getId()));
customerService.deleteCustomer(c2.getId());
Assertions.assertEquals(2, customerService.findAllCustomers().size());
Customer c4 = customerService.updateCustomer(c3.getId(), "fourth", "4");
Assertions.assertNotEquals(c3.getUsername(), c4.getUsername());
Assertions.assertNotEquals(c3.getPassword(), c4.getPassword());
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
}
@Test
void testPost() {
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
Customer c1 = customerService.addCustomer("first", "1");
Customer c2 = customerService.addCustomer("second", "2");
Post p1 = postService.addPost(c1, "first title", "nonsense");
Post p2 = postService.addPost(c2, "second title", "ordinal");
Assertions.assertEquals(2, postService.findAllPosts().size());
Assertions.assertEquals(p1.getCustomer(), c1);
Assertions.assertEquals(p2.getCustomer(), c2);
Assertions.assertEquals(c1.getPosts().get(0), p1);
Assertions.assertEquals(c2.getPosts().get(0), p2);
Assertions.assertEquals(p1, postService.findPost(p1.getId()));
Assertions.assertEquals(p2, postService.findPost(p2.getId()));
Post p3 = postService.addPost(c1, "asdf", "asd");
postService.deletePost(p1.getId());
Assertions.assertEquals(1, customerService.findCustomer(c1.getId()).getPosts().size());
Post p4 = postService.updatePost(p2.getId(), "third title", "wow");
Assertions.assertNotEquals(p2.getTitle(), p4.getTitle());
Assertions.assertNotEquals(p2.getContent(), p4.getContent());
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
}
@Test
void testComment() {
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
Customer c1 = customerService.addCustomer("first", "1");
Customer c2 = customerService.addCustomer("second", "2");
Post p1 = postService.addPost(c1, "first title", "nonsense");
Post p2 = postService.addPost(c2, "second title", "ordinal");
Assertions.assertEquals(2, postService.findAllPosts().size());
Comment com1 = commentService.addComment(c1, p2, "What");
Comment com2 = commentService.addComment(c2, p1, "How");
Assertions.assertEquals(c1, p2.getComments().get(0).getCustomer());
Assertions.assertEquals(c2, p1.getComments().get(0).getCustomer());
Comment com3 = commentService.addComment(c1, p1, "Really");
Assertions.assertEquals(com2, commentService.findComment(p1.getComments().get(0).getId()));
Comment com4 = commentService.updateComment(com3.getId(), "Not really");
Assertions.assertNotEquals(com3.getContent(), com4.getContent());
Assertions.assertEquals(com3.getCustomer().getId(), com4.getCustomer().getId());
commentService.deleteAllComments();
postService.deleteAllPosts();
customerService.deleteAllCustomers();
}
}

View File

@ -1,13 +0,0 @@
package np.something;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SomethingApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -0,0 +1,6 @@
spring.datasource.url=jdbc:h2:mem:testdb
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=create-drop