Skip to content

Commit 9735509

Browse files
Brutus5000bukajsytlos
authored andcommitted
Refactor clan creation to be done via Elide POST call
+ other cleanups
1 parent 6c6f388 commit 9735509

9 files changed

Lines changed: 253 additions & 94 deletions

File tree

src/main/java/com/faforever/api/clan/ClanService.java

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,77 @@
1010
import com.faforever.api.error.ApiException;
1111
import com.faforever.api.error.Error;
1212
import com.faforever.api.error.ErrorCode;
13-
import com.faforever.api.error.ProgrammingError;
14-
import com.faforever.api.player.PlayerRepository;
1513
import com.faforever.api.player.PlayerService;
1614
import com.faforever.api.security.JwtService;
1715
import com.fasterxml.jackson.databind.ObjectMapper;
1816
import lombok.RequiredArgsConstructor;
1917
import lombok.SneakyThrows;
18+
import org.springframework.security.jwt.Jwt;
2019
import org.springframework.security.core.Authentication;
2120
import org.springframework.stereotype.Service;
21+
import org.springframework.transaction.annotation.Transactional;
22+
import org.springframework.util.Assert;
2223

2324
import java.time.Instant;
2425
import java.time.temporal.ChronoUnit;
2526
import java.util.Objects;
2627
import java.util.Set;
28+
import java.util.List;
2729

2830
@Service
2931
@RequiredArgsConstructor
3032
public class ClanService {
3133

3234
private final ClanRepository clanRepository;
33-
private final PlayerRepository playerRepository;
3435
private final FafApiProperties fafApiProperties;
3536
private final JwtService jwtService;
3637
private final ObjectMapper objectMapper;
3738
private final PlayerService playerService;
3839
private final ClanMembershipRepository clanMembershipRepository;
3940

41+
@Transactional
42+
public void preCreate(Clan clan) {
43+
Assert.isNull(clan.getId(), "Clan payload with id can not be used for creation.");
44+
45+
Player player = playerService.getCurrentPlayer();
46+
47+
if (player.getClanMembership() != null) {
48+
throw ApiException.of(ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN);
49+
}
50+
51+
if (!player.equals(clan.getFounder())) {
52+
throw ApiException.of(ErrorCode.CLAN_INVALID_FOUNDER);
53+
}
54+
55+
clanRepository.findOneByName(clan.getName()).ifPresent(c -> {
56+
throw ApiException.of(ErrorCode.CLAN_NAME_EXISTS, clan.getName());
57+
});
58+
59+
clanRepository.findOneByTag(clan.getTag()).ifPresent(c -> {
60+
throw ApiException.of(ErrorCode.CLAN_TAG_EXISTS, clan.getTag());
61+
});
62+
63+
clan.setLeader(player);
64+
clan.setMemberships(List.of(new ClanMembership()
65+
.setClan(clan)
66+
.setPlayer(player)));
67+
}
68+
4069
@SneakyThrows
41-
Clan create(String name, String tag, String description, Player creator) {
70+
@Transactional
71+
@Deprecated
72+
// use preCreate instead
73+
Clan create(String name, String tag, String description) {
74+
Player creator = playerService.getCurrentPlayer();
75+
4276
if (creator.getClanMembership() != null) {
43-
throw new ApiException(new Error(ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN));
77+
throw ApiException.of(ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN);
4478
}
4579
if (clanRepository.findOneByName(name).isPresent()) {
46-
throw new ApiException(new Error(ErrorCode.CLAN_NAME_EXISTS, name));
80+
throw ApiException.of(ErrorCode.CLAN_NAME_EXISTS, name);
4781
}
4882
if (clanRepository.findOneByTag(tag).isPresent()) {
49-
throw new ApiException(new Error(ErrorCode.CLAN_TAG_EXISTS, tag));
83+
throw ApiException.of(ErrorCode.CLAN_TAG_EXISTS, tag);
5084
}
5185

5286
Clan clan = new Clan();
@@ -70,16 +104,18 @@ Clan create(String name, String tag, String description, Player creator) {
70104
}
71105

72106
@SneakyThrows
73-
String generatePlayerInvitationToken(Player requester, int newMemberId, int clanId) {
107+
@Transactional
108+
String generatePlayerInvitationToken(int newMemberId, int clanId) {
109+
Player requester = playerService.getCurrentPlayer();
110+
74111
Clan clan = clanRepository.findById(clanId)
75112
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
76113

77114
if (!requester.getId().equals(clan.getLeader().getId())) {
78-
throw new ApiException(new Error(ErrorCode.CLAN_NOT_LEADER, clanId));
115+
throw ApiException.of(ErrorCode.CLAN_NOT_LEADER, clanId);
79116
}
80117

81-
Player newMember = playerRepository.findById(newMemberId)
82-
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_GENERATE_LINK_PLAYER_NOT_FOUND, newMemberId)));
118+
Player newMember = playerService.getById(newMemberId);
83119

84120
long expire = Instant.now()
85121
.plus(fafApiProperties.getClan().getInviteLinkExpireDurationMinutes(), ChronoUnit.MINUTES)
@@ -92,28 +128,26 @@ String generatePlayerInvitationToken(Player requester, int newMemberId, int clan
92128
}
93129

94130
@SneakyThrows
95-
void acceptPlayerInvitationToken(String stringToken, Authentication authentication) {
96-
String decodedToken = jwtService.decodeAndVerify(stringToken);
97-
InvitationResult invitation = objectMapper.readValue(decodedToken, InvitationResult.class);
131+
void acceptPlayerInvitationToken(String stringToken) {
132+
Jwt token = jwtService.decodeAndVerify(stringToken);
133+
InvitationResult invitation = objectMapper.readValue(token.getClaims(), InvitationResult.class);
98134

99135
if (invitation.isExpired()) {
100-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE));
136+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE);
101137
}
102138

103139
final Integer clanId = invitation.clan().id();
104-
Player player = playerService.getPlayer(authentication);
140+
Player player = playerService.getCurrentPlayer();
105141
Clan clan = clanRepository.findById(clanId)
106142
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
107143

108-
Player newMember = playerRepository.findById(invitation.newMember().id())
109-
.orElseThrow(() -> new ProgrammingError("ClanMember does not exist: " + invitation.newMember().id()));
110-
144+
Player newMember = playerService.getById(invitation.newMember().getId());
111145

112146
if (!Objects.equals(player.getId(), newMember.getId())) {
113-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER));
147+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER);
114148
}
115149
if (newMember.getClan() != null) {
116-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN));
150+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN);
117151
}
118152

119153
ClanMembership membership = new ClanMembership();

src/main/java/com/faforever/api/clan/ClansController.java

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@
1111
import io.swagger.annotations.ApiResponses;
1212
import lombok.RequiredArgsConstructor;
1313
import org.springframework.security.access.prepost.PreAuthorize;
14-
import org.springframework.security.core.Authentication;
15-
import org.springframework.transaction.annotation.Transactional;
1614
import org.springframework.web.bind.annotation.GetMapping;
1715
import org.springframework.web.bind.annotation.PostMapping;
1816
import org.springframework.web.bind.annotation.RequestMapping;
1917
import org.springframework.web.bind.annotation.RequestParam;
2018
import org.springframework.web.bind.annotation.RestController;
2119

22-
import java.io.IOException;
2320
import java.io.Serializable;
2421
import java.util.Map;
2522

@@ -40,8 +37,9 @@ public class ClansController {
4037
@ApiResponse(code = 200, message = "Success with JSON { player: {id: ?, login: ?}, clan: { id: ?, name: ?, tag: ?}}"),
4138
@ApiResponse(code = 400, message = "Bad Request")})
4239
@GetMapping(path = "/me", produces = APPLICATION_JSON_VALUE)
43-
public MeResult me(Authentication authentication) {
44-
Player player = playerService.getPlayer(authentication);
40+
@Deprecated // use regular /me route instead
41+
public MeResult me() {
42+
Player player = playerService.getCurrentPlayer();
4543

4644
Clan clan = player.getClan();
4745
ClanResult clanResult = null;
@@ -59,13 +57,11 @@ public MeResult me(Authentication authentication) {
5957
@ApiResponse(code = 400, message = "Bad Request")})
6058
@PostMapping(path = "/create", produces = APPLICATION_JSON_VALUE)
6159
@PreAuthorize("hasRole('ROLE_USER')")
62-
@Transactional
60+
@Deprecated // use POST /data/clans instead (with a founder in relationships)
6361
public Map<String, Serializable> createClan(@RequestParam(value = "name") String name,
6462
@RequestParam(value = "tag") String tag,
65-
@RequestParam(value = "description", required = false) String description,
66-
Authentication authentication) throws IOException {
67-
Player player = playerService.getPlayer(authentication);
68-
Clan clan = clanService.create(name, tag, description, player);
63+
@RequestParam(value = "description", required = false) String description) {
64+
Clan clan = clanService.create(name, tag, description);
6965
return Map.of("id", clan.getId(), "type", "clan");
7066
}
7167

@@ -76,19 +72,14 @@ public Map<String, Serializable> createClan(@RequestParam(value = "name") String
7672
@GetMapping(path = "/generateInvitationLink", produces = APPLICATION_JSON_VALUE)
7773
public Map<String, Serializable> generateInvitationLink(
7874
@RequestParam(value = "clanId") int clanId,
79-
@RequestParam(value = "playerId") int newMemberId,
80-
Authentication authentication) throws IOException {
81-
Player player = playerService.getPlayer(authentication);
82-
String jwtToken = clanService.generatePlayerInvitationToken(player, newMemberId, clanId);
75+
@RequestParam(value = "playerId") int newMemberId) {
76+
String jwtToken = clanService.generatePlayerInvitationToken(newMemberId, clanId);
8377
return Map.of("jwtToken", jwtToken);
8478
}
8579

86-
@ApiOperation("Check invitation link and add Member to Clan")
80+
@ApiOperation("Check invitation link and add member to Clan")
8781
@PostMapping(path = "/joinClan", produces = APPLICATION_JSON_VALUE)
88-
@Transactional
89-
public void joinClan(
90-
@RequestParam(value = "token") String stringToken,
91-
Authentication authentication) throws IOException {
92-
clanService.acceptPlayerInvitationToken(stringToken, authentication);
82+
public void joinClan(@RequestParam(value = "token") String stringToken) {
83+
clanService.acceptPlayerInvitationToken(stringToken);
9384
}
9485
}

src/main/java/com/faforever/api/data/domain/Clan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public class Clan extends AbstractEntity<Clan> implements OwnableEntity {
4646
private String description;
4747
private String tagColor;
4848
private String websiteUrl;
49+
private Boolean requiresInvitation = Boolean.TRUE;
4950
private Set<ClanMembership> memberships;
50-
private Boolean requiresInvitation;
5151

5252
@Column(name = "name")
5353
@NotNull

src/main/java/com/faforever/api/data/listeners/ClanEnricherListener.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
package com.faforever.api.data.listeners;
22

3+
import com.faforever.api.clan.ClanService;
34
import com.faforever.api.config.FafApiProperties;
45
import com.faforever.api.data.domain.Clan;
56
import org.springframework.stereotype.Component;
67

78
import jakarta.inject.Inject;
89
import jakarta.persistence.PostLoad;
10+
import jakarta.persistence.PrePersist;
911

1012
@Component
1113
public class ClanEnricherListener {
1214

1315
private static FafApiProperties fafApiProperties;
16+
private static ClanService clanService;
1417

1518
@Inject
16-
public void init(FafApiProperties fafApiProperties) {
19+
public void init(FafApiProperties fafApiProperties, ClanService clanService) {
1720
ClanEnricherListener.fafApiProperties = fafApiProperties;
21+
ClanEnricherListener.clanService = clanService;
22+
}
23+
24+
@PrePersist
25+
public void prePersist(Clan clan) {
26+
if (clan.getId() == null) {
27+
clanService.preCreate(clan);
28+
}
1829
}
1930

2031
@PostLoad

src/main/java/com/faforever/api/error/ErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public enum ErrorCode {
6161
CLAN_ACCEPT_PLAYER_IN_A_CLAN(152, "Player is in a clan", "You are already in a clan"),
6262
CLAN_NOT_LEADER(153, "You Permission", "You are not the leader of the clan"),
6363
CLAN_NOT_EXISTS(154, "Cannot find Clan", "Clan with id {0, number} is not available"),
64-
CLAN_GENERATE_LINK_PLAYER_NOT_FOUND(155, "Player not found", "Cannot find player with id {0, number} who should be invited to the clan"),
64+
PLAYER_NOT_FOUND(155, "Player not found", "Cannot find player with id {0, number}."),
6565
CLAN_NAME_EXISTS(156, "Clan Name already in use", "The clan name ''{0}'' is already in use. Please choose a different clan name."),
6666
CLAN_TAG_EXISTS(157, "Clan Tag already in use", "The clan tag ''{0}'' is already in use. Please choose a different clan tag."),
6767
VALIDATION_FAILED(158, "Validation failed", "{0}"),
@@ -117,6 +117,7 @@ public enum ErrorCode {
117117
LESS_PERMISSIVE_LICENSE(207, "Less permissive license", "New license is less permissive than current license."),
118118
MALFORMED_URL(208, "Malformed URL", "Provided url ''{0}'' is malformed."),
119119
NOT_ALLOWED_URL_HOST(209, "URL host not allowed", "Provided URL's host is not allowed. URL: ''{0}'', allowed hosts: ''{1}''."),
120+
CLAN_INVALID_FOUNDER(210, "Invalid clan founder", "If you create a clan you must be the founder of it."),
120121
;
121122

122123

src/main/java/com/faforever/api/player/PlayerService.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@
22

33
import com.faforever.api.data.domain.Player;
44
import com.faforever.api.error.ApiException;
5-
import com.faforever.api.error.Error;
65
import com.faforever.api.error.ErrorCode;
76
import com.faforever.api.security.FafAuthenticationToken;
87
import lombok.RequiredArgsConstructor;
98
import org.springframework.security.core.Authentication;
9+
import org.springframework.security.core.context.SecurityContextHolder;
1010
import org.springframework.stereotype.Service;
1111

12-
import static com.faforever.api.error.ErrorCode.TOKEN_INVALID;
1312

1413
@Service
1514
@RequiredArgsConstructor
1615
public class PlayerService {
1716

1817
private final PlayerRepository playerRepository;
1918

20-
public Player getPlayer(Authentication authentication) {
19+
public Player getPlayer() {
20+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
21+
2122
if (authentication instanceof FafAuthenticationToken fafAuthenticationToken) {
2223
return playerRepository.findById((fafAuthenticationToken.getUserId()))
23-
.orElseThrow(() -> new ApiException(new Error(TOKEN_INVALID)));
24+
.orElseThrow(() -> ApiException.of(ErrorCode.TOKEN_INVALID));
2425
}
25-
throw new ApiException(new Error(TOKEN_INVALID));
26+
throw ApiException.of(ErrorCode.TOKEN_INVALID);
2627
}
2728

2829
public Player getById(Integer playerId) {
2930
return playerRepository.findById(playerId)
30-
.orElseThrow(() -> new ApiException(new Error(ErrorCode.ENTITY_NOT_FOUND, playerId)));
31+
.orElseThrow(() -> ApiException.of(ErrorCode.PLAYER_NOT_FOUND, playerId));
3132
}
3233
}

src/main/java/com/faforever/api/voting/VotingController.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import io.swagger.annotations.ApiOperation;
1010
import lombok.RequiredArgsConstructor;
1111
import org.springframework.security.access.prepost.PreAuthorize;
12-
import org.springframework.security.core.Authentication;
1312
import org.springframework.web.bind.annotation.RequestBody;
1413
import org.springframework.web.bind.annotation.RequestMapping;
1514
import org.springframework.web.bind.annotation.RequestMethod;
@@ -31,14 +30,14 @@ public class VotingController {
3130
@ApiOperation(value = "Post a vote")
3231
@PreAuthorize("hasScope('" + OAuthScope._VOTE + "')")
3332
@RequestMapping(path = "/vote", method = RequestMethod.POST, produces = JsonApiMediaType.JSON_API_MEDIA_TYPE)
34-
public void postVote(@RequestBody Vote vote, Authentication authentication) {
35-
votingService.saveVote(vote, playerService.getPlayer(authentication));
33+
public void postVote(@RequestBody Vote vote) {
34+
votingService.saveVote(vote, playerService.getCurrentPlayer());
3635
}
3736

3837
@ApiOperation(value = "See if user can vote on a subject")
3938
@RequestMapping(path = "/votingSubjectsAbleToVote", method = RequestMethod.GET, produces = JsonApiMediaType.JSON_API_MEDIA_TYPE)
40-
public void votingSubjectsAbleTo(HttpServletResponse response, Authentication authentication, HttpServletRequest request) throws IOException {
41-
List<VotingSubject> votingSubjects = votingService.votingSubjectsAbleToVote(playerService.getPlayer(authentication));
39+
public void votingSubjectsAbleTo(HttpServletResponse response, HttpServletRequest request) throws IOException {
40+
List<VotingSubject> votingSubjects = votingService.votingSubjectsAbleToVote(playerService.getCurrentPlayer());
4241
redirectToFilteredVotingSubjects(response, votingSubjects, request);
4342
}
4443

0 commit comments

Comments
 (0)