Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
20a0a11
dev/codeforces/ Добавил RestClient
AlexanderGarifullin Dec 15, 2024
43380cc
dev/codeforces/ Добавил шаблон для работы с Codeforces Api
AlexanderGarifullin Dec 15, 2024
3293399
dev/codeforces/ Разделил Codeforces пакеты репозиториев и моделей на …
AlexanderGarifullin Dec 15, 2024
ccf6888
dev/codeforces/ Сделал бины из сервисов CfTeamService и CfUserService
AlexanderGarifullin Dec 15, 2024
ac43cd2
dev/codeforces/ Удалил неактуальные файлы
AlexanderGarifullin Dec 16, 2024
138aad3
dev/codeforces/ Добавил новые модели
AlexanderGarifullin Dec 16, 2024
eaa3de7
dev/codeforces/ Обновил базу данных
AlexanderGarifullin Dec 16, 2024
8ea2b38
dev/codeforces/ Обновил базу данных 2
AlexanderGarifullin Dec 16, 2024
67c0d7a
dev/codeforces/ Сделал репозитории для новых моделей
AlexanderGarifullin Dec 16, 2024
01c0e02
dev/codeforces/ Добавил payload для новых моделей
AlexanderGarifullin Dec 16, 2024
a72be82
dev/codeforces/ Добавил сервисы для новых моделей
AlexanderGarifullin Dec 16, 2024
5c7ae68
dev/codeforces/ Добавил контроллеры для новых моделей
AlexanderGarifullin Dec 16, 2024
10f844c
dev/codeforces/ Добавил маппер для Group
AlexanderGarifullin Dec 16, 2024
5b5a955
dev/codeforces/ Добавил исключения для Group
AlexanderGarifullin Dec 16, 2024
c34c3d4
dev/codeforces/ Добавил приватный конструктор для GroupMapper
AlexanderGarifullin Dec 16, 2024
2abd0bc
dev/codeforces/ Исправил классы по checkStyle
AlexanderGarifullin Dec 16, 2024
bc7190f
dev/codeforces/ Добавил ControllerAdvice для Codeforces Exceptions
AlexanderGarifullin Dec 16, 2024
3458a09
dev/codeforces/ Удалил лишние импорты
AlexanderGarifullin Dec 16, 2024
8d84a2f
dev/codeforces/ Отрефакторил util классы
AlexanderGarifullin Dec 16, 2024
582256d
dev/codeforces/test Добавил модульные тесты для GroupService
AlexanderGarifullin Dec 16, 2024
d64ff32
dev/codeforces/test Добавил модульные тесты для GroupMapper
AlexanderGarifullin Dec 16, 2024
dcae5c8
dev/codeforces/test Немного отрефакторил GroupServiceTest
AlexanderGarifullin Dec 16, 2024
061b7f8
dev/codeforces/test Добавил unit тесты для GroupController
AlexanderGarifullin Dec 16, 2024
5322744
dev/codeforces/test Добавил lombock для тестов и замеил throws на @Sn…
AlexanderGarifullin Dec 16, 2024
c58a76f
dev/codeforces/test Написал интеграционные тесты для GroupController
AlexanderGarifullin Dec 16, 2024
e06188a
dev/codeforces/test Исправил интеграционные тесты и код под checkStyle
AlexanderGarifullin Dec 16, 2024
c116e74
dev/codeforces/ Добавил Response классы для моделей
AlexanderGarifullin Dec 16, 2024
7193466
dev/codeforces/ Добавил кэширование для запроса получения рейтинга по…
AlexanderGarifullin Dec 16, 2024
c6efd66
dev/codeforces/ Добавил ошибки для работы с API
AlexanderGarifullin Dec 16, 2024
1ea7521
dev/codeforces/ Немного изменил модели бд
AlexanderGarifullin Dec 16, 2024
a2a81a2
dev/codeforces/ Добавил мапперы для превращения модели в response
AlexanderGarifullin Dec 16, 2024
123c991
dev/codeforces/ Добавил сервисы для моделей
AlexanderGarifullin Dec 16, 2024
824c2a6
dev/codeforces/ Добавил DTO для api
AlexanderGarifullin Dec 16, 2024
2b176b6
dev/codeforces/ Добавил контроллеры
AlexanderGarifullin Dec 16, 2024
cd8c00b
dev/codeforces/ Актуализировал тесты
AlexanderGarifullin Dec 16, 2024
0109a2d
dev/codeforces/ Исправил код под checkStyle
AlexanderGarifullin Dec 16, 2024
1ab0f1a
dev/codeforces/ Добавил логику для Team
AlexanderGarifullin Dec 16, 2024
e125655
dev/codeforces/ Добавил логику для Players
AlexanderGarifullin Dec 17, 2024
4ed4224
dev/codeforces/ Отрефакторил код под checkStyle
AlexanderGarifullin Dec 17, 2024
71c3299
dev/codeforces/ Добавил тесты для TeamMapper
AlexanderGarifullin Dec 17, 2024
6a94375
dev/codeforces/ Добавил тесты для PlayerMapper
AlexanderGarifullin Dec 17, 2024
7e00f7c
dev/codeforces/ Добавил тесты для RatingCalculator
AlexanderGarifullin Dec 17, 2024
be1df51
dev/codeforces/test Удалил лишние импорты
AlexanderGarifullin Dec 17, 2024
80538c5
dev/codeforces/test Удалил лишние импорты
AlexanderGarifullin Dec 17, 2024
d055d97
dev/codeforces/test Написать unit тесты для TeamController
AlexanderGarifullin Dec 17, 2024
43a77e5
dev/codeforces/test Написать unit тесты для PlayerController
AlexanderGarifullin Dec 17, 2024
e0d4372
dev/codeforces/test Отрефакторил GroupServiceTest
AlexanderGarifullin Dec 17, 2024
ca912d2
dev/codeforces/test Написал unit тесты для Teamservice
AlexanderGarifullin Dec 17, 2024
9bcbc04
dev/codeforces/test Удалил лишние импорты
AlexanderGarifullin Dec 17, 2024
66a5249
dev/codeforces/test Исправил ошибку в названии теста
AlexanderGarifullin Dec 17, 2024
4c3a62e
dev/codeforces/test Написат unit тесты для PlayerService
AlexanderGarifullin Dec 17, 2024
f0c092c
dev/codeforces/ref удалил лишние импорты
AlexanderGarifullin Dec 17, 2024
b44bc08
dev/codeforces/test Написал интеграционные тесты для TeamController
AlexanderGarifullin Dec 17, 2024
d1e4eb5
dev/codeforces/test Исправил тесты и код под checkStyle
AlexanderGarifullin Dec 17, 2024
f4d696a
dev/codeforces/test Исправил тест deleteTeam_successв TeamControllerI…
AlexanderGarifullin Dec 17, 2024
718b7aa
dev/codeforces/test Написал тесты для CodeforcesClient
AlexanderGarifullin Dec 17, 2024
46ca4ff
dev/codeforces/test Немного отрефакторил код
AlexanderGarifullin Dec 17, 2024
cdd2495
dev/codeforces/test Добавил профиль test тесту CodeforcesClientTest
AlexanderGarifullin Dec 17, 2024
305dfac
dev/codeforces/test попытка исправить CodeforcesClientTest
AlexanderGarifullin Dec 17, 2024
700d626
dev/codeforces/test Написал интеграционные тесты для PlayerController…
AlexanderGarifullin Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-impl:0.12.6'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.6'

// cache
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:junit-jupiter:1.20.1'
testImplementation 'org.wiremock:wiremock-standalone:3.9.1'
testImplementation 'org.wiremock.integrations.testcontainers:wiremock-testcontainers-module:1.0-alpha-14'
testImplementation 'org.testcontainers:postgresql:1.20.3'
testImplementation 'org.liquibase:liquibase-core:4.29.2'
testImplementation 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
Expand Down
24 changes: 12 additions & 12 deletions config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@

<!-- https://checkstyle.org/config_misc.html -->
<!-- Check for trailing spaces. -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- <module name="RegexpSingleline">-->
<!-- <property name="format" value="\s+$"/>-->
<!-- <property name="minimum" value="0"/>-->
<!-- <property name="maximum" value="0"/>-->
<!-- <property name="message" value="Line has trailing spaces."/>-->
<!-- </module>-->


<!-- https://checkstyle.org/config_filters.html -->
Expand Down Expand Up @@ -277,12 +277,12 @@
<!-- <module name="IllegalTokenText"/> -->
<!-- <module name="IllegalType"/> -->
<module name="InnerAssignment"/>
<module name="MagicNumber">
<property name="ignoreHashCodeMethod" value="true"/>
<property name="ignoreAnnotation" value="true"/>
<message key="magic.number"
value="Most likely the value ''{0}'' is a configuration one. You should move it to configuration file instead of source code."/>
</module>
<!-- <module name="MagicNumber">-->
<!-- <property name="ignoreHashCodeMethod" value="true"/>-->
<!-- <property name="ignoreAnnotation" value="true"/>-->
<!-- <message key="magic.number"-->
<!-- value="Most likely the value ''{0}'' is a configuration one. You should move it to configuration file instead of source code."/>-->
<!-- </module>-->
<!-- <module name="MissingCtor"/> -->
<module name="MissingSwitchDefault"/>
<module name="ModifiedControlVariable"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.cf.cfteam.advicers.codeforces;

import com.cf.cfteam.exceptions.codeforces.*;
import com.cf.cfteam.utils.ErrorResponseBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class CodeforcesExceptionHandler {
private static final String ID = "id";
private static final String LOGIN = "login";
private static final String TEAM_ID = "teamId";
private static final String PLAYER_ID = "playerId";

@ExceptionHandler(GroupNotFoundException.class)
public ResponseEntity<Object> handleUserNotFoundException(GroupNotFoundException ex) {
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND, Map.of(ID, ex.getId()));
}

@ExceptionHandler(TeamNotFoundException.class)
public ResponseEntity<Object> handleTeamNotFoundException(TeamNotFoundException ex) {
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND, Map.of(ID, ex.getId()));
}

@ExceptionHandler(PlayerNotFoundException.class)
public ResponseEntity<Object> handlePayerNotFoundException(PlayerNotFoundException ex) {
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND, Map.of(ID, ex.getId()));
}

@ExceptionHandler(PlayerAlreadyInTeamException.class)
public ResponseEntity<Object> handlePlayerAlreadyInTeamException(PlayerAlreadyInTeamException ex) {
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.CONFLICT,
Map.of(LOGIN, ex.getLogin()));
}

@ExceptionHandler(PlayerNotFromTeamException.class)
public ResponseEntity<Object> handlePlayerNotFromTeamException(PlayerNotFromTeamException ex) {
Map<String, Object> details = new HashMap<>();
if (ex.getPlayerId() != null) {
details.put(PLAYER_ID, ex.getPlayerId());
}
if (ex.getTeamId() != null) {
details.put(TEAM_ID, ex.getTeamId());
}

return ErrorResponseBuilder.buildErrorResponse(
ex.getMessage(),
HttpStatus.CONFLICT,
details
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import com.cf.cfteam.exceptions.security.TokenRevokedException;
import com.cf.cfteam.exceptions.security.UserAlreadyRegisterException;
import com.cf.cfteam.exceptions.security.UserNotFoundException;
import com.cf.cfteam.utils.ErrorResponseBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -18,44 +18,39 @@ public class SecurityExceptionHandler {

private static final String LOGIN = "login";
private static final String TOKEN = "token";
private static final String UNEXPECTED_ERROR = "unexpected.error";
private static final String ID = "id";

@ExceptionHandler(UserAlreadyRegisterException.class)
public ResponseEntity<Object> handleUserAlreadyRegisterException(UserAlreadyRegisterException ex) {
return buildErrorResponse(ex.getMessage(), HttpStatus.CONFLICT, Map.of(LOGIN, ex.getLogin()));
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.CONFLICT, Map.of(LOGIN,
ex.getLogin()));
}

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex) {
return buildErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND, Map.of(LOGIN, ex.getLogin()));
Map<String, Object> details = new HashMap<>();
if (ex.getLogin() != null) {
details.put(LOGIN, ex.getLogin());
}
if (ex.getId() != null) {
details.put(ID, ex.getId());
}

return ErrorResponseBuilder.buildErrorResponse(
ex.getMessage(),
HttpStatus.NOT_FOUND,
details
);
}

@ExceptionHandler(TokenRevokedException.class)
public ResponseEntity<Object> handleTokenRevokedException(TokenRevokedException ex) {
return buildErrorResponse(ex.getMessage(), HttpStatus.UNAUTHORIZED, Map.of(TOKEN, ex.getToken()));
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.UNAUTHORIZED, Map.of(TOKEN,
ex.getToken()));
}

@ExceptionHandler(InvalidTwoFactorCodeException.class)
public ResponseEntity<Object> handleInvalidTwoFactorCodeException(InvalidTwoFactorCodeException ex) {
return buildErrorResponse(ex.getMessage(), HttpStatus.BAD_REQUEST, null);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleGenericException(Exception ex) {
return buildErrorResponse(UNEXPECTED_ERROR, HttpStatus.INTERNAL_SERVER_ERROR, null);
}

private ResponseEntity<Object> buildErrorResponse(String message, HttpStatus status, Map<String, Object> details) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("timestamp", LocalDateTime.now());
errorResponse.put("status", status.value());
errorResponse.put("error", status.getReasonPhrase());
errorResponse.put("message", message);

if (details != null) {
errorResponse.put("details", details);
}

return new ResponseEntity<>(errorResponse, status);
return ErrorResponseBuilder.buildErrorResponse(ex.getMessage(), HttpStatus.BAD_REQUEST, null);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/cf/cfteam/config/AppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

import com.cf.cfteam.services.security.MyUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.client.RestClient;


@Configuration
Expand Down Expand Up @@ -41,4 +45,12 @@ public UserDetailsService userDetailsService() {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public RestClient restClient(@Value("${codeforces.api.base.url}") String url) {
return RestClient.builder()
.baseUrl(url)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/cf/cfteam/config/CacheConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.cf.cfteam.config;

import com.cf.cfteam.services.client.CodeforcesClient;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@RequiredArgsConstructor
public class CacheConfig {

private final CodeforcesClient codeforcesClient;

@Bean
public LoadingCache<String, Double> ratingCache() {
return Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.maximumSize(1000)
.build(codeforcesClient::fetchRatingFromApi);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.cf.cfteam.controllers.codeforces;

import com.cf.cfteam.services.codeforces.GroupService;
import com.cf.cfteam.transfer.payloads.codeforces.GroupPayload;
import com.cf.cfteam.transfer.responses.codeforces.GroupResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("api/cf/groups")
public class GroupController {

private final GroupService groupService;

@GetMapping("/user/{userId}")
public ResponseEntity<List<GroupResponse>> getAllGroupsByUser(@PathVariable Long userId,
Authentication authentication) {
List<GroupResponse> groups = groupService.getAllGroupsByUser(userId);
return ResponseEntity.ok(groups);
}

@GetMapping("/{groupId}")
public ResponseEntity<GroupResponse> getGroupById(@PathVariable Long groupId, Authentication authentication) {
GroupResponse group = groupService.getGroupById(groupId);
return ResponseEntity.ok(group);
}

@PostMapping("/user/{userId}")
public ResponseEntity<GroupResponse> addGroupToUser(@PathVariable Long userId,
@RequestBody GroupPayload groupPayload,
Authentication authentication) {
GroupResponse createdGroup = groupService.addGroupToUser(userId, groupPayload);
return ResponseEntity.ok(createdGroup);
}

@PutMapping("/{groupId}")
public ResponseEntity<GroupResponse> updateGroup(@PathVariable Long groupId, @RequestBody GroupPayload groupPayload,
Authentication authentication) {
GroupResponse group = groupService.updateGroup(groupId, groupPayload);
return ResponseEntity.ok(group);
}

@DeleteMapping("/{groupId}")
public ResponseEntity<Void> deleteGroup(@PathVariable Long groupId, Authentication authentication) {
groupService.deleteGroup(groupId);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/user/{userId}")
public ResponseEntity<Void> deleteAllGroupsByUser(@PathVariable Long userId, Authentication authentication) {
groupService.deleteAllGroupsByUser(userId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.cf.cfteam.controllers.codeforces;

import com.cf.cfteam.services.codeforces.PlayerService;
import com.cf.cfteam.transfer.payloads.codeforces.PlayerPayload;
import com.cf.cfteam.transfer.responses.codeforces.PlayerResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("api/cf/players")
public class PlayerController {

private final PlayerService playerService;

@GetMapping("/team/{teamId}")
public ResponseEntity<List<PlayerResponse>> getAllPlayersByTeam(@PathVariable Long teamId,
Authentication authentication) {
List<PlayerResponse> players = playerService.getAllPlayersByTeam(teamId);
return ResponseEntity.ok(players);
}

@GetMapping("/{playerId}")
public ResponseEntity<PlayerResponse> getPlayerById(@PathVariable Long playerId, Authentication authentication) {
PlayerResponse player = playerService.getPlayerById(playerId);
return ResponseEntity.ok(player);
}

@PostMapping("/team/{teamId}")
public ResponseEntity<PlayerResponse> addPlayerToTeam(@PathVariable Long teamId,
@RequestBody PlayerPayload playerPayload,
Authentication authentication) {
PlayerResponse player = playerService.addPlayerToTeam(teamId, playerPayload);
return ResponseEntity.ok(player);
}

@PutMapping("/players/{playerId}/teams/{teamId}")
public ResponseEntity<PlayerResponse> updatePlayerInTeam(@PathVariable Long playerId,
@PathVariable Long teamId,
@RequestBody PlayerPayload playerPayload,
Authentication authentication) {
PlayerResponse player = playerService.updatePlayerInTeam(playerId, teamId, playerPayload);
return ResponseEntity.ok(player);
}

@DeleteMapping("/players/{playerId}/teams/{teamId}")
public ResponseEntity<Void> deletePlayerFromTeam(@PathVariable Long playerId,
@PathVariable Long teamId,
Authentication authentication) {
playerService.deletePlayerFromTeam(playerId, teamId);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/team/{teamId}")
public ResponseEntity<Void> deleteAllPlayersFromTeam(@PathVariable Long teamId, Authentication authentication) {
playerService.deleteAllPlayersFromTeam(teamId);
return ResponseEntity.noContent().build();
}
}
Loading