diff --git a/server/pom.xml b/server/pom.xml
index 5ab001a..c5d9654 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -84,6 +84,10 @@
postgresql
test
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
diff --git a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java b/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java
index ee746ac..ada70c3 100644
--- a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java
+++ b/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java
@@ -1,19 +1,98 @@
package ru.tubryansk.tdms.config;
+import jakarta.servlet.http.HttpSessionEvent;
+import jakarta.servlet.http.HttpSessionListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
+import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
+
@Configuration
public class SecurityConfiguration {
@Bean
- SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
- .authorizeHttpRequests(req -> req.requestMatchers("/**").permitAll())
- .csrf(AbstractHttpConfigurer::disable).build();
+ .authorizeHttpRequests(this::configureHttpAuthorization)
+ .csrf(AbstractHttpConfigurer::disable)
+ .cors(AbstractHttpConfigurer::disable)
+ .authenticationManager(authenticationManager())
+ .sessionManagement(this::configureSessionManagement)
+ .build();
+ }
+
+ private void configureHttpAuthorization(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry httpAuthorization) {
+ /* API ROUTES */
+ httpAuthorization.requestMatchers("/api/diploma-topic/**").permitAll();
+ httpAuthorization.requestMatchers("/api/**").denyAll();
+ /* STATIC ROUTES */
+ httpAuthorization.requestMatchers("/**").permitAll();
+ /* OTHER */
+ httpAuthorization.anyRequest().denyAll();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager() {
+
+ return new ProviderManager(authenticationProvider());
+ }
+
+ private AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder());
+ provider.setUserDetailsService(userDetailsService());
+ return provider;
+ }
+
+ private UserDetailsService userDetailsService() {
+ return new InMemoryUserDetailsManager(User.builder()
+ .username("admin")
+ .password("{noop}admin")
+ .authorities("ROLE_STUDENT", "ROLE_TEACHER", "ROLE_CURATOR")
+ .build());
+ }
+
+ private PasswordEncoder passwordEncoder() {
+ return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ }
+
+ @Bean
+ // todo: remove when login/logout is implemented
+ public HttpSessionListener autoAuthenticateUnderAdmin() {
+ return new HttpSessionListener() {
+ @Override
+ public void sessionCreated(HttpSessionEvent se) {
+ SecurityContext context = SecurityContextHolder.createEmptyContext();
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("admin", "admin");
+ Authentication authenticated = authenticationManager().authenticate(authentication);
+ context.setAuthentication(authenticated);
+ SecurityContextHolder.setContext(context);
+ se.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
+ }
+ };
+ }
+
+ // todo: remove when login/logout is implemented, since we do not need automatically created session with no authentication
+ private void configureSessionManagement(SessionManagementConfigurer sessionManagement) {
+ sessionManagement.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}
diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicRestController.java b/server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicRestController.java
new file mode 100644
index 0000000..996ad1f
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicRestController.java
@@ -0,0 +1,33 @@
+package ru.tubryansk.tdms.controller;
+
+
+import jakarta.validation.constraints.PositiveOrZero;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
+import ru.tubryansk.tdms.service.DiplomaTopicService;
+
+import java.util.List;
+
+
+@RestController
+@RequestMapping("/api/diploma-topic/")
+@Validated
+public class DiplomaTopicRestController {
+ @Autowired
+ private DiplomaTopicService diplomaTopicService;
+
+ @GetMapping("/get-all")
+ public List getAll() {
+ return diplomaTopicService.getAll();
+ }
+
+ @GetMapping("/get-by-id/{id:[\\-+]?\\d+}")
+ public DiplomaTopicDTO getById(@PathVariable @PositiveOrZero Integer id) {
+ return diplomaTopicService.getById(id);
+ }
+}
diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/TestController.java b/server/src/main/java/ru/tubryansk/tdms/controller/TestController.java
deleted file mode 100644
index d2abf1a..0000000
--- a/server/src/main/java/ru/tubryansk/tdms/controller/TestController.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package ru.tubryansk.tdms.controller;
-
-
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-
-@Controller
-@RequestMapping("/")
-public class TestController {
- @GetMapping
- public String root() {
- return "index.html";
- }
-}
diff --git a/server/src/main/java/ru/tubryansk/tdms/dto/DiplomaTopicDTO.java b/server/src/main/java/ru/tubryansk/tdms/dto/DiplomaTopicDTO.java
index ede40ed..93e01bd 100644
--- a/server/src/main/java/ru/tubryansk/tdms/dto/DiplomaTopicDTO.java
+++ b/server/src/main/java/ru/tubryansk/tdms/dto/DiplomaTopicDTO.java
@@ -1,15 +1,16 @@
package ru.tubryansk.tdms.dto;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Builder;
+import ru.tubryansk.tdms.entity.DiplomaTopic;
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class DiplomaTopicDTO {
- private Integer id;
- private String name;
+@Builder
+public record DiplomaTopicDTO(Integer id, String name) {
+ public static DiplomaTopicDTO fromEntity(DiplomaTopic diplomaTopic) {
+ return DiplomaTopicDTO.builder()
+ .id(diplomaTopic.getId())
+ .name(diplomaTopic.getName())
+ .build();
+ }
}
diff --git a/server/src/main/java/ru/tubryansk/tdms/dto/ErrorResponse.java b/server/src/main/java/ru/tubryansk/tdms/dto/ErrorResponse.java
new file mode 100644
index 0000000..c993f8e
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/dto/ErrorResponse.java
@@ -0,0 +1,20 @@
+package ru.tubryansk.tdms.dto;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+
+public record ErrorResponse(String message, ErrorCode errorCode) {
+ @RequiredArgsConstructor
+ @Getter
+ public enum ErrorCode {
+ BAD_REQUEST(HttpStatus.BAD_REQUEST),
+ VALIDATION_ERROR(HttpStatus.BAD_REQUEST),
+ INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
+ NOT_FOUND(HttpStatus.NOT_FOUND),
+ ACCESS_DENIED(HttpStatus.FORBIDDEN)
+ ;
+
+ private final HttpStatus httpStatus;
+ }
+}
\ No newline at end of file
diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java b/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java
new file mode 100644
index 0000000..8c28bef
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java
@@ -0,0 +1,14 @@
+package ru.tubryansk.tdms.exception;
+
+import ru.tubryansk.tdms.dto.ErrorResponse;
+
+public class AccessDeniedException extends BusinessException {
+ public AccessDeniedException() {
+ super("Access denied");
+ }
+
+ @Override
+ public ErrorResponse.ErrorCode getErrorCode() {
+ return ErrorResponse.ErrorCode.ACCESS_DENIED;
+ }
+}
diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java b/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java
new file mode 100644
index 0000000..10dbe2b
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java
@@ -0,0 +1,13 @@
+package ru.tubryansk.tdms.exception;
+
+import ru.tubryansk.tdms.dto.ErrorResponse;
+
+public class BusinessException extends RuntimeException {
+ public BusinessException(String message) {
+ super(message);
+ }
+
+ public ErrorResponse.ErrorCode getErrorCode() {
+ return ErrorResponse.ErrorCode.INTERNAL_ERROR;
+ }
+}
diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java b/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..288e4d5
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java
@@ -0,0 +1,44 @@
+package ru.tubryansk.tdms.exception;
+
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.resource.NoResourceFoundException;
+import ru.tubryansk.tdms.dto.ErrorResponse;
+
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+ // todo: make a better error message
+ return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.VALIDATION_ERROR);
+ }
+
+ @ExceptionHandler(BusinessException.class)
+ public ErrorResponse handleBusinessException(BusinessException e, HttpServletResponse response) {
+ response.setStatus(e.getErrorCode().getHttpStatus().value());
+ return new ErrorResponse(e.getMessage(), e.getErrorCode());
+ }
+
+ @ExceptionHandler(NoResourceFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ErrorResponse handleNoResourceFoundException(NoResourceFoundException e) {
+ // todo: make error page
+ return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.NOT_FOUND);
+ }
+
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public ErrorResponse handleUnexpectedException(Exception e) {
+ // todo: make error page
+ log.error("Unexpected exception.", e);
+ return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR);
+ }
+}
diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java b/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java
new file mode 100644
index 0000000..1fd0cfa
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java
@@ -0,0 +1,14 @@
+package ru.tubryansk.tdms.exception;
+
+import ru.tubryansk.tdms.dto.ErrorResponse;
+
+public class NotFoundException extends BusinessException {
+ public NotFoundException(Class> entityClass, Integer id) {
+ super(entityClass.getSimpleName() + " with id " + id + " not found");
+ }
+
+ @Override
+ public ErrorResponse.ErrorCode getErrorCode() {
+ return ErrorResponse.ErrorCode.NOT_FOUND;
+ }
+}
diff --git a/server/src/main/java/ru/tubryansk/tdms/repository/DiplomaTopicRepository.java b/server/src/main/java/ru/tubryansk/tdms/repository/DiplomaTopicRepository.java
new file mode 100644
index 0000000..49a1306
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/repository/DiplomaTopicRepository.java
@@ -0,0 +1,9 @@
+package ru.tubryansk.tdms.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import ru.tubryansk.tdms.entity.DiplomaTopic;
+
+@Repository
+public interface DiplomaTopicRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java b/server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java
new file mode 100644
index 0000000..d43cb9e
--- /dev/null
+++ b/server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java
@@ -0,0 +1,31 @@
+package ru.tubryansk.tdms.service;
+
+import jakarta.transaction.Transactional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import ru.tubryansk.tdms.entity.DiplomaTopic;
+import ru.tubryansk.tdms.exception.NotFoundException;
+import ru.tubryansk.tdms.repository.DiplomaTopicRepository;
+import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+public class DiplomaTopicService {
+ @Autowired
+ private DiplomaTopicRepository diplomaTopicRepository;
+
+ public List getAll() {
+ return diplomaTopicRepository.findAll()
+ .stream()
+ .map(DiplomaTopicDTO::fromEntity)
+ .collect(Collectors.toList());
+ }
+
+ public DiplomaTopicDTO getById(Integer id) {
+ return DiplomaTopicDTO.fromEntity(diplomaTopicRepository.findById(id)
+ .orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id)));
+ }
+}