Initial commit
This commit is contained in:
32 files changed
+1950
No files matched your search
+48
@@ -0,0 +1,48 @@
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# Eclipse m2e generated files
|
||||
# Eclipse Core
|
||||
.project
|
||||
# JDT-specific (Eclipse Java Development Tools)
|
||||
.classpath
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# Use an official Maven image to build the application
|
||||
FROM maven:3.9-eclipse-temurin-23-alpine AS build
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the source code into the container
|
||||
COPY . /app
|
||||
|
||||
# Run Maven to package the application (this will generate a JAR file in the target/ folder)
|
||||
RUN mvn clean package -DskipTests
|
||||
|
||||
# Use a lightweight JDK image to run the app
|
||||
FROM eclipse-temurin:23-alpine
|
||||
|
||||
# Set the working directory for the final image
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the JAR file from the build stage
|
||||
COPY --from=build /app/target/*.jar /app/w-server.jar
|
||||
|
||||
# Expose port of Spring Boot app
|
||||
EXPOSE 8090
|
||||
|
||||
# Run the Spring Boot application
|
||||
ENTRYPOINT ["java", "-jar", "w-server.jar"]
|
||||
@@ -0,0 +1,26 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
w-server:
|
||||
build: .
|
||||
ports:
|
||||
- "80:8090"
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- mariadb
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.5
|
||||
container_name: mariadb
|
||||
env_file: .env
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
restart: always
|
||||
env_file: .env
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- mariadb
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.ermnvldmr.w</groupId>
|
||||
<artifactId>w-server</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>W</name>
|
||||
<description>Server for the W network</description>
|
||||
<properties>
|
||||
<java.version>23</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>4.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>properties-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>initialize</phase>
|
||||
<goals>
|
||||
<goal>read-project-properties</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<files>
|
||||
<file>profile.properties</file>
|
||||
</files>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
Whitespace-only changes.
@@ -0,0 +1,34 @@
|
||||
package com.ermnvldmr.w;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@SuppressWarnings("unused")
|
||||
public class DatabaseInitializer implements ApplicationRunner {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public DatabaseInitializer(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
String columnName = "password_sha";
|
||||
String tableName = "user";
|
||||
|
||||
// Check if the column already exists
|
||||
String checkColumnSql = "SELECT COUNT(*) FROM information_schema.COLUMNS " +
|
||||
"WHERE TABLE_NAME = ? AND COLUMN_NAME = ?";
|
||||
|
||||
Integer count = jdbcTemplate.queryForObject(checkColumnSql, new Object[]{tableName, columnName}, Integer.class);
|
||||
|
||||
// If the column does not exist, add it
|
||||
if (count != null && count == 0) {
|
||||
String sql = "ALTER TABLE `" + tableName + "` ADD `" + columnName + "` VARCHAR(64) NULL AFTER `login`;";
|
||||
jdbcTemplate.execute(sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ermnvldmr.w;
|
||||
|
||||
import com.ermnvldmr.w.config.PropertiesConfig;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(PropertiesConfig.class)
|
||||
public class WApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(com.ermnvldmr.w.WApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ermnvldmr.w.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "config")
|
||||
@SuppressWarnings("unused")
|
||||
public class PropertiesConfig {
|
||||
private String mediaDir;
|
||||
private String jwtSecret;
|
||||
private String shaSalt;
|
||||
|
||||
public String getMediaDir() {
|
||||
return mediaDir;
|
||||
}
|
||||
|
||||
public void setMediaDir(String mediaDir) {
|
||||
this.mediaDir = mediaDir;
|
||||
}
|
||||
|
||||
public String getJwtSecret() {
|
||||
return jwtSecret;
|
||||
}
|
||||
|
||||
public void setJwtSecret(String jwtSecret) {
|
||||
this.jwtSecret = jwtSecret;
|
||||
}
|
||||
|
||||
public String getShaSalt() {
|
||||
return shaSalt;
|
||||
}
|
||||
|
||||
public void setShaSalt(String shaSalt) {
|
||||
this.shaSalt = shaSalt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.ermnvldmr.w.controller;
|
||||
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.service.DiscussionService;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
import com.ermnvldmr.w.service.PostService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.ermnvldmr.w.utils.Utils.getValidationErrors;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@SuppressWarnings("unused")
|
||||
public class DiscussionController {
|
||||
private final DiscussionService discussionService;
|
||||
private final PostService postService;
|
||||
private final UserService userService;
|
||||
|
||||
public DiscussionController(DiscussionService discussionService,
|
||||
PostService postService,
|
||||
UserService userService) {
|
||||
this.discussionService = discussionService;
|
||||
this.postService = postService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Find discussions by post ID",
|
||||
description = "Retrieves a list of discussions associated with the given post ID.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved discussions"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@GetMapping(value = {"/discussions/post/{id}"})
|
||||
public ResponseEntity<List<Discussion>> findDiscussions(
|
||||
@Parameter(description = "ID of the post to find discussions for", required = true)
|
||||
@PathVariable long id) {
|
||||
Post post = postService.findById(id);
|
||||
if (post == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
|
||||
List<Discussion> discussions = discussionService.findAllByPostId(post.getId());
|
||||
return ResponseEntity.ok(discussions); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Write a new discussion",
|
||||
description = "Creates a new discussion related to a post. Optionally, it can be a reply to an existing discussion.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "201", description = "Discussion created successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "Validation errors or parent discussion not found"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@PostMapping(value = {"/discussions/post/{id}"})
|
||||
public ResponseEntity<Object> writeDiscussion(
|
||||
@Parameter(description = "ID of the post to write the discussion for", required = true)
|
||||
@PathVariable long id,
|
||||
|
||||
@Parameter(description = "Discussion object to be created", required = true)
|
||||
@Valid @RequestBody Discussion discussion,
|
||||
|
||||
@Parameter(description = "JWT token for user authentication", required = true)
|
||||
@RequestParam String jwt,
|
||||
|
||||
@Parameter(description = "Optional parent discussion ID for nested discussions")
|
||||
@RequestParam(required = false) String parentDiscussionId,
|
||||
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(getValidationErrors(bindingResult)); // 400 Bad Request
|
||||
}
|
||||
|
||||
Post post = postService.findById(id);
|
||||
if (post == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).body(null); // 401 Unauthorized
|
||||
}
|
||||
|
||||
if (parentDiscussionId != null) {
|
||||
Discussion parentDiscussion = discussionService.findById(Long.parseLong(parentDiscussionId));
|
||||
if (parentDiscussion == null) {
|
||||
return ResponseEntity.badRequest().build(); // 404 Not Found
|
||||
}
|
||||
discussion.setParentDiscussion(parentDiscussion);
|
||||
}
|
||||
|
||||
discussion.setUser(user);
|
||||
discussion.setPost(post);
|
||||
|
||||
Discussion savedDiscussion = discussionService.writeDiscussion(discussion);
|
||||
return ResponseEntity.status(201).body(savedDiscussion); // 201 Created
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete a discussion",
|
||||
description = "Deletes the specified discussion. Only the author or an admin can perform this action.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Discussion deleted successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "403", description = "Forbidden action"),
|
||||
@ApiResponse(responseCode = "404", description = "Discussion not found")
|
||||
})
|
||||
@DeleteMapping("discussions/{id}")
|
||||
public ResponseEntity<Void> deleteDiscussion(
|
||||
@Parameter(description = "ID of the discussion to delete", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT token for authentication", required = true)
|
||||
@RequestParam String jwt) {
|
||||
User authenticatedUser = userService.findByJwt(jwt);
|
||||
if (authenticatedUser == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Discussion discussionToDelete = discussionService.findById(id);
|
||||
if (discussionToDelete == null) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not found
|
||||
}
|
||||
|
||||
if (authenticatedUser.getId() != discussionToDelete.getUser().getId() && !authenticatedUser.isAdmin()) {
|
||||
return ResponseEntity.status(403).build(); // 403 Forbidden
|
||||
}
|
||||
|
||||
discussionService.deleteDiscussionById(id);
|
||||
return ResponseEntity.ok().build(); // 200 Ok
|
||||
}
|
||||
|
||||
@Operation(summary = "Retrieve all discussions by user ID",
|
||||
description = "Fetches a list of discussions created by a specific user.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved user's discussions"),
|
||||
@ApiResponse(responseCode = "400", description = "Invalid user ID provided"),
|
||||
@ApiResponse(responseCode = "404", description = "No discussions found for the specified user")
|
||||
})
|
||||
@GetMapping("/discussions/user/{userId}")
|
||||
public ResponseEntity<List<Discussion>> findDiscussionsByUserId(
|
||||
@Parameter(description = "ID of the user whose discussions are to be retrieved", required = true)
|
||||
@PathVariable Long userId) {
|
||||
if (userId == null || userId <= 0) {
|
||||
return ResponseEntity.badRequest().build(); // 400 Bad Request
|
||||
}
|
||||
|
||||
List<Discussion> discussions = discussionService.findAllByUserId(userId);
|
||||
if (discussions == null || discussions.isEmpty()) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not Found
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(discussions); // 200 OK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.ermnvldmr.w.controller;
|
||||
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.form.WritePostForm;
|
||||
import com.ermnvldmr.w.service.PostService;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ermnvldmr.w.utils.Utils.getValidationErrors;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@SuppressWarnings("unused")
|
||||
public class PostController {
|
||||
private final PostService postService;
|
||||
private final UserService userService;
|
||||
|
||||
public PostController(PostService postService, UserService userService) {
|
||||
this.postService = postService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Retrieve all posts",
|
||||
description = "Fetches a list of all posts.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved posts")
|
||||
})
|
||||
@GetMapping("posts")
|
||||
public ResponseEntity<List<Post>> findPosts() {
|
||||
List<Post> posts = postService.findAll();
|
||||
return ResponseEntity.ok(posts); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Create a new post",
|
||||
description = "Writes a new post. Requires user authentication via JWT.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "201", description = "Post created successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "Validation errors"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "500", description = "Internal server error due to media upload failure")
|
||||
})
|
||||
@PostMapping("posts")
|
||||
public ResponseEntity<Object> writePost(
|
||||
@Parameter(description = "Post data to create a new post", required = true)
|
||||
@Valid @RequestBody WritePostForm postForm,
|
||||
|
||||
@Parameter(description = "JWT token for user authentication", required = true)
|
||||
@RequestParam String jwt,
|
||||
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(getValidationErrors(bindingResult)); // 400 Bad Request
|
||||
}
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
try {
|
||||
Post createdPost = postService.writePost(postForm, user);
|
||||
return ResponseEntity.status(201).body(createdPost); // 201 Created
|
||||
} catch (IOException e) {
|
||||
return ResponseEntity.status(500).body("Media upload failed."); // 500 Internal Server Error
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "Find a post by ID",
|
||||
description = "Retrieves a specific post by its ID, incrementing its view count.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved post"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@GetMapping(value = {"/posts/{id}"})
|
||||
public ResponseEntity<Post> findPost(
|
||||
@Parameter(description = "ID of the post to retrieve", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT token for view mapping")
|
||||
@RequestParam String jwt,
|
||||
HttpServletRequest request) {
|
||||
final String viewerIdentifier = getViewerIdentifier(jwt, request);
|
||||
Post post = postService.getAndIncrementViewCount(id, viewerIdentifier);
|
||||
if (post == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
return ResponseEntity.ok(post); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete a post",
|
||||
description = "Deletes the specified post. Only the author or an admin can perform this action.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Post deleted successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "403", description = "Forbidden action"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@DeleteMapping("posts/{id}")
|
||||
public ResponseEntity<Void> deletePost(
|
||||
@Parameter(description = "ID of the post to delete", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT token for authentication", required = true)
|
||||
@RequestParam String jwt) {
|
||||
User authenticatedUser = userService.findByJwt(jwt);
|
||||
if (authenticatedUser == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Post postToDelete = postService.findById(id);
|
||||
if (postToDelete == null) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not found
|
||||
}
|
||||
|
||||
if (authenticatedUser.getId() != postToDelete.getUser().getId() && !authenticatedUser.isAdmin()) {
|
||||
return ResponseEntity.status(403).build(); // 403 Forbidden
|
||||
}
|
||||
|
||||
postService.deletePostById(id);
|
||||
return ResponseEntity.ok().build(); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Retrieve all posts by user ID",
|
||||
description = "Fetches a list of posts created by a specific user.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved user's posts"),
|
||||
@ApiResponse(responseCode = "400", description = "Invalid user ID provided"),
|
||||
@ApiResponse(responseCode = "404", description = "No posts found for the specified user")
|
||||
})
|
||||
@GetMapping("/posts/user/{userId}")
|
||||
public ResponseEntity<List<Post>> findPostsByUserId(
|
||||
@Parameter(description = "ID of the user whose posts are to be retrieved", required = true)
|
||||
@PathVariable Long userId) {
|
||||
if (userId == null || userId <= 0) {
|
||||
return ResponseEntity.badRequest().build(); // 400 Bad Request
|
||||
}
|
||||
|
||||
List<Post> posts = postService.findAllByUserId(userId);
|
||||
if (posts == null || posts.isEmpty()) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not Found
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(posts); // 200 OK
|
||||
}
|
||||
|
||||
private String getViewerIdentifier(String jwt, HttpServletRequest request) {
|
||||
if (jwt != null && !jwt.isBlank()) {
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user != null) {
|
||||
return "user-" + user.getId();
|
||||
}
|
||||
}
|
||||
return "ip-" + request.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.ermnvldmr.w.controller;
|
||||
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.form.UserCredentialsEditForm;
|
||||
import com.ermnvldmr.w.form.UserCredentialsEnter;
|
||||
import com.ermnvldmr.w.form.UserCredentialsRegister;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
import com.ermnvldmr.w.form.validator.UserCredentialsEnterValidator;
|
||||
import com.ermnvldmr.w.form.validator.UserCredentialsRegisterValidator;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.ermnvldmr.w.utils.Utils.getValidationErrors;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@SuppressWarnings("unused")
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
private final UserCredentialsEnterValidator userCredentialsEnterValidator;
|
||||
private final UserCredentialsRegisterValidator userCredentialsRegisterValidator;
|
||||
|
||||
public UserController(UserService userService,
|
||||
UserCredentialsEnterValidator userCredentialsEnterValidator,
|
||||
UserCredentialsRegisterValidator userCredentialsRegisterValidator) {
|
||||
this.userService = userService;
|
||||
this.userCredentialsEnterValidator = userCredentialsEnterValidator;
|
||||
this.userCredentialsRegisterValidator = userCredentialsRegisterValidator;
|
||||
}
|
||||
|
||||
@InitBinder("userCredentialsEnter")
|
||||
public void initEnterBinder(WebDataBinder binder) {
|
||||
binder.addValidators(userCredentialsEnterValidator);
|
||||
}
|
||||
|
||||
@InitBinder("userCredentialsRegister")
|
||||
public void initRegisterBinder(WebDataBinder binder) {
|
||||
binder.addValidators(userCredentialsRegisterValidator);
|
||||
}
|
||||
|
||||
@Operation(summary = "Authenticate user and retrieve JWT",
|
||||
description = "Validates user credentials and returns a JWT if successful.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "JWT retrieved successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "Validation errors"),
|
||||
@ApiResponse(responseCode = "401", description = "Wrong login or password")
|
||||
})
|
||||
@PostMapping("users/enter")
|
||||
public ResponseEntity<Object> findUserJwt(
|
||||
@Parameter(description = "User credentials for login", required = true)
|
||||
@RequestBody @Valid UserCredentialsEnter userCredentials,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(getValidationErrors(bindingResult)); // 400 Bad Request
|
||||
}
|
||||
|
||||
User user = userService.findByLoginAndPassword(userCredentials.getLogin(), userCredentials.getPassword());
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).body("Wrong login or password."); // 401 Unauthorized
|
||||
}
|
||||
|
||||
String jwt = userService.createUserJwt(user);
|
||||
return ResponseEntity.ok(jwt); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Retrieve user by JWT",
|
||||
description = "Finds a user based on the provided JWT.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "User retrieved successfully"),
|
||||
@ApiResponse(responseCode = "404", description = "User not found")
|
||||
})
|
||||
@GetMapping("users/jwt")
|
||||
public ResponseEntity<User> findUserByJwt(
|
||||
@Parameter(description = "JWT for the user", required = true)
|
||||
@RequestParam String jwt) {
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
return ResponseEntity.ok(user); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Retrieve all users",
|
||||
description = "Fetches a list of all registered users.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Successfully retrieved users")
|
||||
})
|
||||
@GetMapping("users")
|
||||
public ResponseEntity<List<User>> findUsers() {
|
||||
List<User> users = this.userService.findAll();
|
||||
return ResponseEntity.ok(users); // 200 OK
|
||||
}
|
||||
|
||||
@Operation(summary = "Register a new user",
|
||||
description = "Creates a new user account using the provided credentials.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "201", description = "User created successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "Validation errors")
|
||||
})
|
||||
@PostMapping("users/register")
|
||||
public ResponseEntity<Object> addUser(
|
||||
@Parameter(description = "User credentials for registration", required = true)
|
||||
@RequestBody @Valid UserCredentialsRegister userCredentials,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(getValidationErrors(bindingResult)); // 400 Bad Request
|
||||
}
|
||||
|
||||
User newUser = userService.addUserByCredentials(userCredentials);
|
||||
return ResponseEntity.status(201).body(newUser); // 201 Created
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete a user",
|
||||
description = "Deletes the specified user. Only the user themselves or an admin can perform this action.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "User deleted successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "403", description = "Forbidden action"),
|
||||
@ApiResponse(responseCode = "404", description = "User not found")
|
||||
})
|
||||
@DeleteMapping("users/{id}")
|
||||
public ResponseEntity<Void> deleteUser(
|
||||
@Parameter(description = "ID of the user to delete", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT token for authentication", required = true)
|
||||
@RequestParam String jwt) {
|
||||
User authenticatedUser = userService.findByJwt(jwt);
|
||||
if (authenticatedUser == null) {
|
||||
return ResponseEntity.status(401).build(); // 41 Unauthorized
|
||||
}
|
||||
|
||||
User userToDelete = userService.findById(id);
|
||||
if (userToDelete == null) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not found
|
||||
}
|
||||
|
||||
if (authenticatedUser.getId() != userToDelete.getId() && !authenticatedUser.isAdmin()) {
|
||||
return ResponseEntity.status(403).build(); // 403 Forbidden
|
||||
}
|
||||
|
||||
userService.deleteUserById(id);
|
||||
return ResponseEntity.ok().build(); // 200 Ok
|
||||
}
|
||||
|
||||
@Operation(summary = "Edit user profile",
|
||||
description = "Allows a user to edit their profile or an admin to edit any user's profile.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "User profile updated successfully"),
|
||||
@ApiResponse(responseCode = "400", description = "Validation errors"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "403", description = "Forbidden action"),
|
||||
@ApiResponse(responseCode = "404", description = "User not found")
|
||||
})
|
||||
@PutMapping("users/{id}")
|
||||
public ResponseEntity<Object> editUserProfile(
|
||||
@Parameter(description = "ID of the user to edit", required = true) @PathVariable long id,
|
||||
@Parameter(description = "Updated user profile", required = true) @RequestBody @Valid UserCredentialsEditForm userProfileEdit,
|
||||
BindingResult bindingResult,
|
||||
@Parameter(description = "JWT token for authentication", required = true) @RequestParam String jwt) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResponseEntity.badRequest().body(getValidationErrors(bindingResult)); // 400 Bad Request
|
||||
}
|
||||
|
||||
User authenticatedUser = userService.findByJwt(jwt);
|
||||
if (authenticatedUser == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
User userToEdit = userService.findById(id);
|
||||
if (userToEdit == null) {
|
||||
return ResponseEntity.status(404).build(); // 404 Not Found
|
||||
}
|
||||
|
||||
if (authenticatedUser.getId() != userToEdit.getId() && !authenticatedUser.isAdmin()) {
|
||||
return ResponseEntity.status(403).build(); // 403 Forbidden
|
||||
}
|
||||
|
||||
userToEdit.setName(userProfileEdit.getName());
|
||||
userService.saveUser(userToEdit);
|
||||
|
||||
return ResponseEntity.ok(userToEdit); // 200 OK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.ermnvldmr.w.controller;
|
||||
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.domain.Vote;
|
||||
import com.ermnvldmr.w.service.DiscussionService;
|
||||
import com.ermnvldmr.w.service.PostService;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
import com.ermnvldmr.w.service.VoteService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@SuppressWarnings("unused")
|
||||
public class VoteController {
|
||||
private final DiscussionService discussionService;
|
||||
private final PostService postService;
|
||||
private final UserService userService;
|
||||
private final VoteService voteService;
|
||||
|
||||
public VoteController(DiscussionService discussionService,
|
||||
PostService postService,
|
||||
UserService userService,
|
||||
VoteService voteService) {
|
||||
this.discussionService = discussionService;
|
||||
this.postService = postService;
|
||||
this.userService = userService;
|
||||
this.voteService = voteService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Vote on a post",
|
||||
description = "Allows a user to upvote or downvote a post.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Vote recorded successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@PostMapping("/votes/post/{id}")
|
||||
public ResponseEntity<Object> votePost(
|
||||
@Parameter(description = "ID of the post to vote on", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT of the user", required = true)
|
||||
@RequestParam String jwt,
|
||||
@Parameter(description = "True for upvote, false for downvote", required = true)
|
||||
@RequestParam boolean upvote) {
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Post post = postService.findById(id);
|
||||
if (post == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
|
||||
Vote vote = voteService.votePost(post, user, upvote);
|
||||
return ResponseEntity.ok(vote); // Return 200 OK with the vote details
|
||||
}
|
||||
|
||||
@Operation(summary = "Vote on a discussion",
|
||||
description = "Allows a user to upvote or downvote a discussion.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Vote recorded successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "404", description = "Discussion not found")
|
||||
})
|
||||
@PostMapping("/votes/discussion/{id}")
|
||||
public ResponseEntity<Object> voteDiscussion(
|
||||
@Parameter(description = "ID of the discussion to vote on", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT of the user", required = true)
|
||||
@RequestParam String jwt,
|
||||
@Parameter(description = "True for upvote, false for downvote", required = true)
|
||||
@RequestParam boolean upvote) {
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Discussion discussion = discussionService.findById(id);
|
||||
if (discussion == null) {
|
||||
return ResponseEntity.notFound().build(); // 404 Not Found
|
||||
}
|
||||
|
||||
Vote vote = voteService.voteDiscussion(discussion, user, upvote);
|
||||
return ResponseEntity.ok(vote); // Return 200 OK with the vote details
|
||||
}
|
||||
|
||||
@Operation(summary = "Get user's vote on a post",
|
||||
description = "Fetches the vote status for a specific post.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Vote details retrieved successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "404", description = "Post not found")
|
||||
})
|
||||
@GetMapping("/votes/post/{id}")
|
||||
public ResponseEntity<Object> getPostVote(
|
||||
@Parameter(description = "ID of the post to check the vote", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT of the user", required = true)
|
||||
@RequestParam String jwt) {
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Post post = postService.findById(id);
|
||||
if (post == null) {
|
||||
return ResponseEntity.badRequest().build(); // 404 Not Found
|
||||
}
|
||||
|
||||
// Fetch the vote status for the post
|
||||
Vote vote = voteService.findByPostAndUser(post, user);
|
||||
return ResponseEntity.ok(vote); // Return 200 OK with the vote details or null if no vote
|
||||
}
|
||||
|
||||
@Operation(summary = "Get user's vote on a discussion",
|
||||
description = "Fetches the vote status for a specific discussion.")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "Vote details retrieved successfully"),
|
||||
@ApiResponse(responseCode = "401", description = "Unauthorized access"),
|
||||
@ApiResponse(responseCode = "404", description = "Discussion not found")
|
||||
})
|
||||
@GetMapping("/votes/discussion/{id}")
|
||||
public ResponseEntity<Object> getDiscussionVote(
|
||||
@Parameter(description = "ID of the discussion to check the vote", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "JWT of the user", required = true)
|
||||
@RequestParam String jwt) {
|
||||
|
||||
User user = userService.findByJwt(jwt);
|
||||
if (user == null) {
|
||||
return ResponseEntity.status(401).build(); // 401 Unauthorized
|
||||
}
|
||||
|
||||
Discussion discussion = discussionService.findById(id);
|
||||
if (discussion == null) {
|
||||
return ResponseEntity.badRequest().build(); // 401 Bad request
|
||||
}
|
||||
|
||||
// Fetch the vote status for the discussion
|
||||
Vote vote = voteService.findByDiscussionAndUser(discussion, user);
|
||||
return ResponseEntity.ok(vote); // Return 200 OK with the vote details or null if no vote
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.ermnvldmr.w.domain;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(indexes = @Index(columnList = "creationTime"))
|
||||
@SuppressWarnings("unused")
|
||||
public class Discussion {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "post_id", nullable = false)
|
||||
private Post post;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "parent_discussion_id")
|
||||
private Discussion parentDiscussion;
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 65000)
|
||||
@Lob
|
||||
private String text;
|
||||
|
||||
@OneToMany(mappedBy = "discussion", cascade = CascadeType.ALL)
|
||||
private final List<Vote> votes = new ArrayList<>();
|
||||
|
||||
@CreationTimestamp
|
||||
private Date creationTime;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setPost(final Post post) {
|
||||
this.post = post;
|
||||
}
|
||||
|
||||
public Long getParentDiscussionId() {
|
||||
return parentDiscussion != null ? parentDiscussion.getId() : null;
|
||||
}
|
||||
|
||||
public void setParentDiscussion(Discussion parentDiscussion) {
|
||||
this.parentDiscussion = parentDiscussion;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public long getVoteCount() {
|
||||
return votes.stream().mapToLong((vote) -> vote.isUpvote() ? 1 : -1).sum();
|
||||
}
|
||||
|
||||
public Date getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public void setCreationTime(Date creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.ermnvldmr.w.domain;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@SuppressWarnings("unused")
|
||||
public class Post {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
|
||||
@NotEmpty
|
||||
@Size(min = 1, max = 100)
|
||||
private String title;
|
||||
|
||||
@NotEmpty
|
||||
@Size(min = 1, max = 10000)
|
||||
@Lob
|
||||
private String text;
|
||||
|
||||
private List<String> media;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
|
||||
@OrderBy("creationTime desc")
|
||||
private List<Discussion> discussions = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
|
||||
private final List<Vote> votes = new ArrayList<>();
|
||||
|
||||
private long viewCount = 0;
|
||||
|
||||
@CreationTimestamp
|
||||
private Date creationTime;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public List<String> getMedia() {
|
||||
return media;
|
||||
}
|
||||
|
||||
public void setMedia(List<String> media) {
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setDiscussions(final List<Discussion> discussions) {
|
||||
this.discussions = discussions;
|
||||
}
|
||||
|
||||
public long getDiscussionCount() {
|
||||
return discussions.size();
|
||||
}
|
||||
|
||||
public long getVoteCount() {
|
||||
return votes.stream().mapToLong((vote) -> vote.isUpvote() ? 1 : -1).sum();
|
||||
}
|
||||
|
||||
public long getViewCount() {
|
||||
return viewCount;
|
||||
}
|
||||
|
||||
public void incrementViewCount() {
|
||||
this.viewCount++;
|
||||
}
|
||||
|
||||
public Date getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public void setCreationTime(Date creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.ermnvldmr.w.domain;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "login"))
|
||||
@SuppressWarnings("unused")
|
||||
public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
|
||||
@NotEmpty
|
||||
@Size(min = 1, max = 100)
|
||||
private String name;
|
||||
|
||||
@NotEmpty
|
||||
@Size(min = 2, max = 24)
|
||||
@Pattern(regexp = "[a-zA-Z]{2,24}")
|
||||
private String login;
|
||||
|
||||
private boolean admin;
|
||||
|
||||
@CreationTimestamp
|
||||
private Date creationTime;
|
||||
|
||||
@OneToMany(mappedBy = "user")
|
||||
private final List<Post> posts = new ArrayList<>();
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
return admin;
|
||||
}
|
||||
|
||||
public void setAdmin(boolean admin) {
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
public Date getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public void setCreationTime(Date creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public void addPost(Post post) {
|
||||
posts.add(post);
|
||||
post.setUser(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ermnvldmr.w.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@SuppressWarnings("unused")
|
||||
public class Vote {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
private boolean upvote; // true for upvote, false for downvote
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "post_id")
|
||||
private Post post;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "discussion_id")
|
||||
private Discussion discussion;
|
||||
|
||||
public boolean isValid() {
|
||||
return (post == null) != (discussion == null); // XOR to ensure only one is set
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public boolean isUpvote() {
|
||||
return upvote;
|
||||
}
|
||||
|
||||
public void setUpvote(boolean upvote) {
|
||||
this.upvote = upvote;
|
||||
}
|
||||
|
||||
public Post getPost() {
|
||||
return post;
|
||||
}
|
||||
|
||||
public void setPost(Post post) {
|
||||
this.post = post;
|
||||
}
|
||||
|
||||
public Discussion getDiscussion() {
|
||||
return discussion;
|
||||
}
|
||||
|
||||
public void setDiscussion(Discussion discussion) {
|
||||
this.discussion = discussion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ermnvldmr.w.form;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class UserCredentialsEditForm {
|
||||
@NotBlank(message = "Name cannot not be blank")
|
||||
@Size(min = 1, max = 64, message = "Name must be of size between 1 and 64 characters")
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ermnvldmr.w.form;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class UserCredentialsEnter {
|
||||
@NotBlank(message = "Login cannot not be blank")
|
||||
@Size(min = 2, max = 24, message = "Login must be of size between 2 and 24 characters")
|
||||
@Pattern(regexp = "[a-zA-Z]{2,24}", message = "Login must only contain latin letters")
|
||||
private String login;
|
||||
|
||||
@NotEmpty(message = "Password cannot be empty")
|
||||
@Size(min = 8, max = 60, message = "Password must be of size between 8 and 60 characters")
|
||||
private String password;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.ermnvldmr.w.form;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class UserCredentialsRegister {
|
||||
|
||||
@NotBlank(message = "Name cannot not be blank")
|
||||
@Size(min = 1, max = 64, message = "Name must be of size between 1 and 64 characters")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "Login cannot not be blank")
|
||||
@Size(min = 2, max = 24, message = "Login must be of size between 2 and 24 characters")
|
||||
@Pattern(regexp = "[a-zA-Z]{2,24}", message = "Login must only contain latin letters")
|
||||
private String login;
|
||||
|
||||
@NotEmpty(message = "Password cannot be empty")
|
||||
@Size(min = 8, max = 60, message = "Password must be of size between 8 and 60 characters")
|
||||
private String password;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ermnvldmr.w.form;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WritePostForm {
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 100)
|
||||
private String title;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10000)
|
||||
@Lob
|
||||
private String text;
|
||||
|
||||
private List<MultipartFile> media;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(final String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(final String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public List<MultipartFile> getMedia() {
|
||||
return media;
|
||||
}
|
||||
|
||||
public void setImage(List<MultipartFile> media) {
|
||||
this.media = media;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ermnvldmr.w.form.validator;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import com.ermnvldmr.w.form.UserCredentialsEnter;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
|
||||
@Component
|
||||
public class UserCredentialsEnterValidator implements Validator {
|
||||
private final UserService userService;
|
||||
|
||||
public UserCredentialsEnterValidator(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return UserCredentialsEnter.class.equals(clazz);
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
if (!errors.hasErrors()) {
|
||||
UserCredentialsEnter enterForm = (UserCredentialsEnter) target;
|
||||
if (userService.findByLoginAndPassword(enterForm.getLogin(), enterForm.getPassword()) == null) {
|
||||
errors.reject("invalid-login-or-password", "Invalid login or password");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ermnvldmr.w.form.validator;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import com.ermnvldmr.w.form.UserCredentialsRegister;
|
||||
import com.ermnvldmr.w.service.UserService;
|
||||
|
||||
@Component
|
||||
public class UserCredentialsRegisterValidator implements Validator {
|
||||
private final UserService userService;
|
||||
|
||||
public UserCredentialsRegisterValidator(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return UserCredentialsRegister.class.equals(clazz);
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
if (!errors.hasErrors()) {
|
||||
UserCredentialsRegister registerForm = (UserCredentialsRegister) target;
|
||||
if (userService.findByLogin(registerForm.getLogin()) != null) {
|
||||
errors.reject("user-exists", "User with such login already exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ermnvldmr.w.repository;
|
||||
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DiscussionRepository extends JpaRepository<Discussion, Long> {
|
||||
@Query(value = "SELECT * FROM discussion WHERE post_id=?1", nativeQuery = true)
|
||||
List<Discussion> findAllByPostId(long postId);
|
||||
|
||||
@Query(value = "SELECT * FROM discussion WHERE user_id=?1", nativeQuery = true)
|
||||
List<Discussion> findAllByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ermnvldmr.w.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PostRepository extends JpaRepository<Post, Long> {
|
||||
List<Post> findAllByOrderByCreationTimeDesc();
|
||||
|
||||
@Query(value = "SELECT * FROM post WHERE user_id=?1", nativeQuery = true)
|
||||
List<Post> findAllByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ermnvldmr.w.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
@Transactional
|
||||
@Modifying
|
||||
@Query(value = "UPDATE user SET password_sha=SHA1(CONCAT(?4, ?2, ?3)) WHERE id=?1", nativeQuery = true)
|
||||
void updatePasswordSha(long id, String login, String password, String salt);
|
||||
|
||||
@Query(value = "SELECT * FROM user WHERE login=?1", nativeQuery = true)
|
||||
User findByLogin(String login);
|
||||
|
||||
@Query(value = "SELECT * FROM user WHERE login=?1 AND password_sha=SHA1(CONCAT(?3, ?1, ?2))", nativeQuery = true)
|
||||
User findByLoginAndPassword(String login, String password, String salt);
|
||||
|
||||
List<User> findAllByOrderByIdDesc();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ermnvldmr.w.repository;
|
||||
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.domain.Vote;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface VoteRepository extends JpaRepository<Vote, Long> {
|
||||
Optional<Vote> findByUserAndPost(User user, Post post);
|
||||
Optional<Vote> findByUserAndDiscussion(User user, Discussion discussion);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ermnvldmr.w.service;
|
||||
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
import com.ermnvldmr.w.repository.DiscussionRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DiscussionService {
|
||||
private final DiscussionRepository discussionRepository;
|
||||
|
||||
public DiscussionService(DiscussionRepository discussionRepository) {
|
||||
this.discussionRepository = discussionRepository;
|
||||
}
|
||||
|
||||
public Discussion findById(long id) {
|
||||
return discussionRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public List<Discussion> findAllByPostId(long postId) { return discussionRepository.findAllByPostId(postId); }
|
||||
|
||||
public Discussion writeDiscussion(Discussion discussion) {
|
||||
return discussionRepository.save(discussion);
|
||||
}
|
||||
|
||||
public void deleteDiscussionById(long id) {
|
||||
discussionRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<Discussion> findAllByUserId(Long userId) {
|
||||
return discussionRepository.findAllByUserId(userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.ermnvldmr.w.service;
|
||||
|
||||
import com.ermnvldmr.w.config.PropertiesConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.form.WritePostForm;
|
||||
import com.ermnvldmr.w.repository.PostRepository;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class PostService {
|
||||
private final PropertiesConfig propertiesConfig;
|
||||
private final PostRepository postRepository;
|
||||
|
||||
private final Map<Long, Map<String, Long>> viewTimestamps = new ConcurrentHashMap<>();
|
||||
|
||||
public PostService(PropertiesConfig propertiesConfig,
|
||||
PostRepository postRepository) {
|
||||
this.propertiesConfig = propertiesConfig;
|
||||
this.postRepository = postRepository;
|
||||
}
|
||||
|
||||
public Post findById(long id) {
|
||||
return postRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public List<Post> findAll() {
|
||||
return postRepository.findAllByOrderByCreationTimeDesc();
|
||||
}
|
||||
|
||||
public Post writePost(WritePostForm form, User author) throws IOException {
|
||||
Post post = new Post();
|
||||
post.setTitle(form.getTitle());
|
||||
post.setText(form.getText());
|
||||
post.setMedia(saveMediaAndGetKeys(form.getMedia(), post.getId() + "_"));
|
||||
post.setUser(author);
|
||||
|
||||
this.postRepository.save(post);
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
public Post getAndIncrementViewCount(Long postId, String viewerIdentifier) {
|
||||
Post post = postRepository.findById(postId).orElse(null);
|
||||
|
||||
if (post != null && shouldIncrementView(postId, viewerIdentifier)) {
|
||||
post.incrementViewCount();
|
||||
postRepository.save(post);
|
||||
}
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
private boolean shouldIncrementView(long postId, String viewerIdentifier) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long viewLimit = TimeUnit.MINUTES.toMillis(10);
|
||||
|
||||
viewTimestamps.putIfAbsent(postId, new ConcurrentHashMap<>());
|
||||
Map<String, Long> postViews = viewTimestamps.get(postId);
|
||||
|
||||
Long lastViewTime = postViews.get(viewerIdentifier);
|
||||
|
||||
if (lastViewTime == null || currentTime - lastViewTime > viewLimit) {
|
||||
postViews.put(viewerIdentifier, currentTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> saveMediaAndGetKeys(List<MultipartFile> media, String keyPrefix) throws IOException {
|
||||
List<String> keys = new ArrayList<>();
|
||||
|
||||
if (media == null || media.isEmpty()) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Get the directory path from config properties
|
||||
Path uploadDir = Paths.get(propertiesConfig.getMediaDir());
|
||||
|
||||
// Ensure the directory exists
|
||||
if (!Files.exists(uploadDir)) {
|
||||
Files.createDirectories(uploadDir);
|
||||
}
|
||||
|
||||
for (int i = 0; i < media.size(); ++i) {
|
||||
var file = media.get(i);
|
||||
|
||||
if (!file.isEmpty()) {
|
||||
// Get the original filename to preserve the extension
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String extension = ""; // Default to an empty string
|
||||
|
||||
if (originalFilename != null) {
|
||||
// Extract the file extension
|
||||
int lastDotIndex = originalFilename.lastIndexOf('.');
|
||||
if (lastDotIndex > 0 && lastDotIndex < originalFilename.length() - 1) {
|
||||
extension = originalFilename.substring(lastDotIndex); // Include the dot
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the full file name, preserving the file extension
|
||||
String key = keyPrefix + i + extension;
|
||||
Path path = uploadDir.resolve(key); // Combine directory and key
|
||||
|
||||
// Write the file
|
||||
Files.write(path, file.getBytes());
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public void deletePostById(long id) {
|
||||
postRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<Post> findAllByUserId(Long userId) {
|
||||
return postRepository.findAllByUserId(userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ermnvldmr.w.service;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTCreationException;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.ermnvldmr.w.config.PropertiesConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.form.UserCredentialsRegister;
|
||||
import com.ermnvldmr.w.repository.UserRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private final PropertiesConfig propertiesConfig;
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final Algorithm algorithm;
|
||||
private final JWTVerifier verifier;
|
||||
|
||||
public UserService(
|
||||
PropertiesConfig propertiesConfig,
|
||||
UserRepository userRepository) {
|
||||
this.propertiesConfig = propertiesConfig;
|
||||
this.userRepository = userRepository;
|
||||
|
||||
this.algorithm = Algorithm.HMAC256(this.propertiesConfig.getJwtSecret());
|
||||
this.verifier = JWT.require(algorithm).build();
|
||||
}
|
||||
|
||||
public User findByLogin(String login) {
|
||||
return login == null ? null : userRepository.findByLogin(login);
|
||||
}
|
||||
|
||||
public User findByLoginAndPassword(String login, String password) {
|
||||
return login != null && password != null ?
|
||||
userRepository.findByLoginAndPassword(login,
|
||||
password,
|
||||
this.propertiesConfig.getShaSalt()) : null;
|
||||
}
|
||||
|
||||
public User findById(Long id) {
|
||||
return id == null ? null : userRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public User findByJwt(String jwt) {
|
||||
try {
|
||||
DecodedJWT decodedJwt = verifier.verify(jwt);
|
||||
return this.findById(decodedJwt.getClaim("userId").asLong());
|
||||
} catch (JWTVerificationException exception){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<User> findAll() {
|
||||
return userRepository.findAllByOrderByIdDesc();
|
||||
}
|
||||
|
||||
public String createUserJwt(User user) {
|
||||
try {
|
||||
return JWT.create()
|
||||
.withClaim("userId", user.getId())
|
||||
.sign(algorithm);
|
||||
} catch (JWTCreationException exception){
|
||||
throw new RuntimeException("Can't create JWT.");
|
||||
}
|
||||
}
|
||||
|
||||
public User addUserByCredentials(UserCredentialsRegister userCredentials) {
|
||||
User user = new User();
|
||||
user.setName(userCredentials.getName());
|
||||
user.setLogin(userCredentials.getLogin());
|
||||
user.setAdmin(false);
|
||||
userRepository.save(user);
|
||||
userRepository.updatePasswordSha(user.getId(),
|
||||
userCredentials.getLogin(),
|
||||
userCredentials.getPassword(),
|
||||
this.propertiesConfig.getShaSalt());
|
||||
return user;
|
||||
}
|
||||
|
||||
public void deleteUserById(long id) {
|
||||
userRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public void saveUser(User user) {
|
||||
userRepository.save(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ermnvldmr.w.service;
|
||||
|
||||
import com.ermnvldmr.w.domain.Discussion;
|
||||
import com.ermnvldmr.w.domain.Post;
|
||||
import com.ermnvldmr.w.domain.User;
|
||||
import com.ermnvldmr.w.domain.Vote;
|
||||
import com.ermnvldmr.w.repository.VoteRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class VoteService {
|
||||
private final VoteRepository voteRepository;
|
||||
|
||||
public VoteService(VoteRepository voteRepository) {
|
||||
this.voteRepository = voteRepository;
|
||||
}
|
||||
|
||||
public Vote findByPostAndUser(Post post, User user) {
|
||||
return voteRepository.findByUserAndPost(user, post).orElse(null);
|
||||
}
|
||||
|
||||
public Vote findByDiscussionAndUser(Discussion discussion, User user) {
|
||||
return voteRepository.findByUserAndDiscussion(user, discussion).orElse(null);
|
||||
}
|
||||
|
||||
public Vote votePost(Post post, User user, boolean upvote) {
|
||||
Optional<Vote> existingVote = voteRepository.findByUserAndPost(user, post);
|
||||
if (existingVote.isPresent()) {
|
||||
Vote vote = existingVote.get();
|
||||
if (vote.isUpvote() == upvote) {
|
||||
voteRepository.delete(vote); // Toggle off the vote if it’s the same type.
|
||||
return null;
|
||||
} else {
|
||||
vote.setUpvote(upvote); // Change vote type if it’s different.
|
||||
return voteRepository.save(vote);
|
||||
}
|
||||
} else {
|
||||
Vote newVote = new Vote();
|
||||
newVote.setUser(user);
|
||||
newVote.setUpvote(upvote);
|
||||
newVote.setPost(post);
|
||||
return voteRepository.save(newVote);
|
||||
}
|
||||
}
|
||||
|
||||
public Vote voteDiscussion(Discussion discussion, User user, boolean upvote) {
|
||||
Optional<Vote> existingVote = voteRepository.findByUserAndDiscussion(user, discussion);
|
||||
if (existingVote.isPresent()) {
|
||||
Vote vote = existingVote.get();
|
||||
if (vote.isUpvote() == upvote) {
|
||||
voteRepository.delete(vote); // Toggle off the vote if it’s the same type.
|
||||
return null;
|
||||
} else {
|
||||
vote.setUpvote(upvote); // Change vote type if it’s different.
|
||||
return voteRepository.save(vote);
|
||||
}
|
||||
} else {
|
||||
Vote newVote = new Vote();
|
||||
newVote.setUser(user);
|
||||
newVote.setUpvote(upvote);
|
||||
newVote.setDiscussion(discussion);
|
||||
return voteRepository.save(newVote);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ermnvldmr.w.utils;
|
||||
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
public static Map<String, Object> getValidationErrors(BindingResult bindingResult) {
|
||||
Map<String, Object> errors = new HashMap<>();
|
||||
bindingResult.getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
|
||||
return Map.of("validationErrors", errors); // Structured JSON format
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
|
||||
spring.datasource.url=${MYSQL_URL}
|
||||
spring.datasource.username=${MYSQL_USER}
|
||||
spring.datasource.password=${MYSQL_PASSWORD}
|
||||
|
||||
spring.jpa.properties.hibernate.show_sql=true
|
||||
spring.jpa.properties.hibernate.use_sql_comments=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
server.port=8090
|
||||
server.error.include-stacktrace=never
|
||||
server.error.whitelabel.enabled=false
|
||||
server.error.include-exception=false
|
||||
|
||||
server.servlet.contextPath=${PATH_PREFIX}
|
||||
|
||||
server.forward-headers-strategy=framework
|
||||
|
||||
config.shaSalt=${SHA_SALT}
|
||||
config.jwtSecret=${JWT_SECRET}
|
||||
config.mediaDir=${MEDIA_DIR}
|
||||
Reference in new issue
Block a user