Skip to content

Commit b8a4226

Browse files
committed
Refactor clan creation to be done via Elide POST call
1 parent 1f1327f commit b8a4226

File tree

12 files changed

+319
-203
lines changed

12 files changed

+319
-203
lines changed

src/inttest/java/com/faforever/api/clan/ClanControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,6 @@ public void createSecondClan() throws Exception {
180180
.andExpect(status().isUnprocessableEntity())
181181
.andReturn();
182182

183-
assertApiError(result, ErrorCode.CLAN_CREATE_CREATOR_IS_IN_A_CLAN);
183+
assertApiError(result, ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN);
184184
}
185185
}

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

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,72 @@
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;
20-
import org.springframework.security.core.Authentication;
2118
import org.springframework.security.jwt.Jwt;
2219
import org.springframework.stereotype.Service;
20+
import org.springframework.util.Assert;
2321

2422
import java.time.Instant;
2523
import java.time.temporal.ChronoUnit;
2624
import java.util.Collections;
25+
import java.util.List;
2726

2827
@Service
2928
@RequiredArgsConstructor
3029
public class ClanService {
3130

3231
private final ClanRepository clanRepository;
33-
private final PlayerRepository playerRepository;
3432
private final FafApiProperties fafApiProperties;
3533
private final JwtService jwtService;
3634
private final ObjectMapper objectMapper;
3735
private final PlayerService playerService;
3836
private final ClanMembershipRepository clanMembershipRepository;
3937

38+
public void preCreate(Clan clan) {
39+
Assert.isNull(clan.getId(), "Clan payload with id can not be used for creation.");
40+
41+
Player player = playerService.getCurrentPlayer();
42+
43+
if (player.getClanMembership() != null) {
44+
throw ApiException.of(ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN);
45+
}
46+
47+
if (!player.equals(clan.getFounder())) {
48+
throw ApiException.of(ErrorCode.CLAN_INVALID_FOUNDER);
49+
}
50+
51+
clanRepository.findOneByName(clan.getName()).ifPresent(c -> {
52+
throw ApiException.of(ErrorCode.CLAN_NAME_EXISTS, clan.getName());
53+
});
54+
55+
clanRepository.findOneByTag(clan.getTag()).ifPresent(c -> {
56+
throw ApiException.of(ErrorCode.CLAN_TAG_EXISTS, clan.getTag());
57+
});
58+
59+
clan.setLeader(player);
60+
clan.setMemberships(List.of(new ClanMembership()
61+
.setClan(clan)
62+
.setPlayer(player)));
63+
}
64+
4065
@SneakyThrows
41-
Clan create(String name, String tag, String description, Player creator) {
66+
@Deprecated
67+
// use preCreate instead
68+
Clan create(String name, String tag, String description) {
69+
Player creator = playerService.getCurrentPlayer();
70+
4271
if (creator.getClanMembership() != null) {
43-
throw new ApiException(new Error(ErrorCode.CLAN_CREATE_CREATOR_IS_IN_A_CLAN));
72+
throw ApiException.of(ErrorCode.CLAN_CREATE_FOUNDER_IS_IN_A_CLAN);
4473
}
4574
if (clanRepository.findOneByName(name).isPresent()) {
46-
throw new ApiException(new Error(ErrorCode.CLAN_NAME_EXISTS, name));
75+
throw ApiException.of(ErrorCode.CLAN_NAME_EXISTS, name);
4776
}
4877
if (clanRepository.findOneByTag(tag).isPresent()) {
49-
throw new ApiException(new Error(ErrorCode.CLAN_TAG_EXISTS, tag));
78+
throw ApiException.of(ErrorCode.CLAN_TAG_EXISTS, tag);
5079
}
5180

5281
Clan clan = new Clan();
@@ -69,16 +98,17 @@ Clan create(String name, String tag, String description, Player creator) {
6998
}
7099

71100
@SneakyThrows
72-
String generatePlayerInvitationToken(Player requester, int newMemberId, int clanId) {
101+
String generatePlayerInvitationToken(int newMemberId, int clanId) {
102+
Player requester = playerService.getCurrentPlayer();
103+
73104
Clan clan = clanRepository.findById(clanId)
74105
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
75106

76107
if (requester.getId() != clan.getLeader().getId()) {
77-
throw new ApiException(new Error(ErrorCode.CLAN_NOT_LEADER, clanId));
108+
throw ApiException.of(ErrorCode.CLAN_NOT_LEADER, clanId);
78109
}
79110

80-
Player newMember = playerRepository.findById(newMemberId)
81-
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_GENERATE_LINK_PLAYER_NOT_FOUND, newMemberId)));
111+
Player newMember = playerService.getById(newMemberId);
82112

83113
long expire = Instant.now()
84114
.plus(fafApiProperties.getClan().getInviteLinkExpireDurationMinutes(), ChronoUnit.MINUTES)
@@ -91,28 +121,26 @@ String generatePlayerInvitationToken(Player requester, int newMemberId, int clan
91121
}
92122

93123
@SneakyThrows
94-
void acceptPlayerInvitationToken(String stringToken, Authentication authentication) {
124+
void acceptPlayerInvitationToken(String stringToken) {
95125
Jwt token = jwtService.decodeAndVerify(stringToken);
96126
InvitationResult invitation = objectMapper.readValue(token.getClaims(), InvitationResult.class);
97127

98128
if (invitation.isExpired()) {
99-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE));
129+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE);
100130
}
101131

102132
final Integer clanId = invitation.getClan().getId();
103-
Player player = playerService.getPlayer(authentication);
133+
Player player = playerService.getCurrentPlayer();
104134
Clan clan = clanRepository.findById(clanId)
105135
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
106136

107-
Player newMember = playerRepository.findById(invitation.getNewMember().getId())
108-
.orElseThrow(() -> new ProgrammingError("ClanMember does not exist: " + invitation.getNewMember().getId()));
109-
137+
Player newMember = playerService.getById(invitation.getNewMember().getId());
110138

111139
if (player.getId() != newMember.getId()) {
112-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER));
140+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER);
113141
}
114142
if (newMember.getClan() != null) {
115-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN));
143+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN);
116144
}
117145

118146
ClanMembership membership = new ClanMembership();

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

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,16 @@
1212
import io.swagger.annotations.ApiResponses;
1313
import lombok.RequiredArgsConstructor;
1414
import org.springframework.security.access.prepost.PreAuthorize;
15-
import org.springframework.security.core.Authentication;
1615
import org.springframework.transaction.annotation.Transactional;
1716
import org.springframework.web.bind.annotation.RequestMapping;
1817
import org.springframework.web.bind.annotation.RequestMethod;
1918
import org.springframework.web.bind.annotation.RequestParam;
2019
import org.springframework.web.bind.annotation.RestController;
2120

22-
import java.io.IOException;
2321
import java.io.Serializable;
2422
import java.util.Map;
2523

26-
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
24+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
2725

2826

2927
@RestController
@@ -37,11 +35,12 @@ public class ClansController {
3735

3836
@ApiOperation("Grab data about yourself and the clan")
3937
@ApiResponses(value = {
40-
@ApiResponse(code = 200, message = "Success with JSON { player: {id: ?, login: ?}, clan: { id: ?, name: ?, tag: ?}}"),
41-
@ApiResponse(code = 400, message = "Bad Request")})
42-
@RequestMapping(path = "/me", method = RequestMethod.GET, produces = APPLICATION_JSON_UTF8_VALUE)
43-
public MeResult me(Authentication authentication) {
44-
Player player = playerService.getPlayer(authentication);
38+
@ApiResponse(code = 200, message = "Success with JSON { player: {id: ?, login: ?}, clan: { id: ?, name: ?, tag: ?}}"),
39+
@ApiResponse(code = 400, message = "Bad Request")})
40+
@RequestMapping(path = "/me", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE)
41+
@Deprecated // use regular /me route instead
42+
public MeResult me() {
43+
Player player = playerService.getCurrentPlayer();
4544

4645
Clan clan = player.getClan();
4746
ClanResult clanResult = null;
@@ -55,44 +54,36 @@ public MeResult me(Authentication authentication) {
5554
// a: the new clan with the leader membership, b: the leader membership with the new clan
5655
@ApiOperation("Create a clan with correct leader, founder and clan membership")
5756
@ApiResponses(value = {
58-
@ApiResponse(code = 200, message = "Success with JSON { id: ?, type: 'clan'}"),
59-
@ApiResponse(code = 400, message = "Bad Request")})
60-
@RequestMapping(path = "/create", method = RequestMethod.POST, produces = APPLICATION_JSON_UTF8_VALUE)
57+
@ApiResponse(code = 200, message = "Success with JSON { id: ?, type: 'clan'}"),
58+
@ApiResponse(code = 400, message = "Bad Request")})
59+
@RequestMapping(path = "/create", method = RequestMethod.POST, produces = APPLICATION_JSON_VALUE)
6160
@PreAuthorize("hasRole('ROLE_USER')")
6261
@Transactional
62+
@Deprecated // use POST /data/clans instead (with a founder in relationships)
6363
public Map<String, Serializable> createClan(@RequestParam(value = "name") String name,
6464
@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);
65+
@RequestParam(value = "description", required = false) String description) {
66+
Player player = playerService.getCurrentPlayer();
67+
Clan clan = clanService.create(name, tag, description);
6968
return ImmutableMap.of("id", clan.getId(), "type", "clan");
7069
}
7170

7271
@ApiOperation("Generate invitation link")
7372
@ApiResponses(value = {
74-
@ApiResponse(code = 200, message = "Success with JSON { jwtToken: ? }"),
75-
@ApiResponse(code = 400, message = "Bad Request")})
76-
@RequestMapping(path = "/generateInvitationLink",
77-
method = RequestMethod.GET,
78-
produces = APPLICATION_JSON_UTF8_VALUE)
73+
@ApiResponse(code = 200, message = "Success with JSON { jwtToken: ? }"),
74+
@ApiResponse(code = 400, message = "Bad Request")})
75+
@RequestMapping(path = "/generateInvitationLink", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE)
7976
public Map<String, Serializable> generateInvitationLink(
80-
@RequestParam(value = "clanId") int clanId,
81-
@RequestParam(value = "playerId") int newMemberId,
82-
Authentication authentication) throws IOException {
83-
Player player = playerService.getPlayer(authentication);
84-
String jwtToken = clanService.generatePlayerInvitationToken(player, newMemberId, clanId);
77+
@RequestParam(value = "clanId") int clanId,
78+
@RequestParam(value = "playerId") int newMemberId) {
79+
String jwtToken = clanService.generatePlayerInvitationToken(newMemberId, clanId);
8580
return ImmutableMap.of("jwtToken", jwtToken);
8681
}
8782

8883
@ApiOperation("Check invitation link and add Member to Clan")
89-
@RequestMapping(path = "/joinClan",
90-
method = RequestMethod.POST,
91-
produces = APPLICATION_JSON_UTF8_VALUE)
84+
@RequestMapping(path = "/joinClan", method = RequestMethod.POST, produces = APPLICATION_JSON_VALUE)
9285
@Transactional
93-
public void joinClan(
94-
@RequestParam(value = "token") String stringToken,
95-
Authentication authentication) throws IOException {
96-
clanService.acceptPlayerInvitationToken(stringToken, authentication);
86+
public void joinClan(@RequestParam(value = "token") String stringToken) {
87+
clanService.acceptPlayerInvitationToken(stringToken);
9788
}
9889
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class Clan extends AbstractEntity implements OwnableEntity {
4747
private String description;
4848
private String tagColor;
4949
private String websiteUrl;
50+
private Boolean requiresInvitation;
5051
private List<ClanMembership> memberships;
5152

5253
@Column(name = "name")
@@ -88,6 +89,11 @@ public String getTagColor() {
8889
return tagColor;
8990
}
9091

92+
@Column(name = "requires_invitation", nullable = false)
93+
public Boolean getRequiresInvitation() {
94+
return requiresInvitation;
95+
}
96+
9197
// Cascading is needed for Create & Delete
9298
@OneToMany(mappedBy = "clan", cascade = CascadeType.ALL, orphanRemoval = true)
9399
// Permission is managed by ClanMembership class

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 javax.inject.Inject;
89
import javax.persistence.PostLoad;
10+
import javax.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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ public enum ErrorCode {
5555
INVALID_METADATA(146, "Invalid metadata", "Metadata is not valid: {0}"),
5656
MAP_RENAME_FAILED(147, "Cannot rename to correct name failed ", "Cannot rename file ''{0}''"),
5757
MAP_INVALID_ZIP(148, "Invalid zip file", "The zip file should only contain one folder at the root level"),
58-
CLAN_CREATE_CREATOR_IS_IN_A_CLAN(149, "You are already in a clan", "Clan creator is already member of a clan"),
58+
CLAN_CREATE_FOUNDER_IS_IN_A_CLAN(149, "You are already in a clan", "Clan creator is already member of a clan"),
5959
CLAN_ACCEPT_TOKEN_EXPIRE(150, "Token Expire", "The Invitation Link expire"),
6060
CLAN_ACCEPT_WRONG_PLAYER(151, "Wrong Player", "Your are not the invited player"),
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}"),
@@ -99,7 +99,8 @@ public enum ErrorCode {
9999
PARSING_LUA_FILE_FAILED(189, "Parsing lua files failed", "During the parsing of the lua file an error occured: {0}"),
100100
NO_RUSH_RADIUS_MISSING(190, "No rush radius missing", "The scenario file must specify a no rush radius"),
101101
INVALID_FEATURED_MOD(191, "Invalid featured mod name", "The featured mod name ''{0}'' is not allowed in this context."),
102-
API_KEY_INVALID(192, "Api key is invalid", "The api key is invalid.");
102+
API_KEY_INVALID(192, "Api key is invalid", "The api key is invalid."),
103+
CLAN_INVALID_FOUNDER(193, "Invalid clan founder", "If you create a clan you must be the founder of it.");
103104

104105
private final int code;
105106
private final String title;

src/main/java/com/faforever/api/map/MapsController.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.swagger.annotations.ApiResponses;
1515
import lombok.AllArgsConstructor;
1616
import lombok.extern.slf4j.Slf4j;
17-
import org.springframework.security.core.Authentication;
1817
import org.springframework.web.bind.annotation.RequestMapping;
1918
import org.springframework.web.bind.annotation.RequestMethod;
2019
import org.springframework.web.bind.annotation.RequestParam;
@@ -77,8 +76,7 @@ public void validateScenarioLua(@RequestParam(name = "scenarioLua") String scena
7776
@ApiResponse(code = 500, message = "Failure")})
7877
@RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_UTF8_VALUE)
7978
public void uploadMap(@RequestParam("file") MultipartFile file,
80-
@RequestParam("metadata") String jsonString,
81-
Authentication authentication) throws IOException {
79+
@RequestParam("metadata") String jsonString) throws IOException {
8280
if (file == null) {
8381
throw new ApiException(new Error(ErrorCode.UPLOAD_FILE_MISSING));
8482
}
@@ -97,7 +95,7 @@ public void uploadMap(@RequestParam("file") MultipartFile file,
9795
throw new ApiException(new Error(ErrorCode.INVALID_METADATA, e.getMessage()));
9896
}
9997

100-
Player player = playerService.getPlayer(authentication);
98+
Player player = playerService.getCurrentPlayer();
10199
mapService.uploadMap(file.getInputStream(), file.getOriginalFilename(), player, ranked);
102100
}
103101
}

src/main/java/com/faforever/api/mod/ModsController.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.faforever.api.player.PlayerService;
88
import com.google.common.io.Files;
99
import io.swagger.annotations.ApiOperation;
10-
import org.springframework.security.core.Authentication;
1110
import org.springframework.web.bind.annotation.RequestMapping;
1211
import org.springframework.web.bind.annotation.RequestMethod;
1312
import org.springframework.web.bind.annotation.RequestParam;
@@ -35,7 +34,7 @@ public ModsController(PlayerService playerService, ModService modService, FafApi
3534

3635
@ApiOperation("Upload a mod")
3736
@RequestMapping(path = "/upload", method = RequestMethod.POST, produces = APPLICATION_JSON_UTF8_VALUE)
38-
public void uploadMod(@RequestParam("file") MultipartFile file, Authentication authentication) throws IOException {
37+
public void uploadMod(@RequestParam("file") MultipartFile file) throws IOException {
3938
if (file == null) {
4039
throw new ApiException(new Error(ErrorCode.UPLOAD_FILE_MISSING));
4140
}
@@ -48,6 +47,6 @@ public void uploadMod(@RequestParam("file") MultipartFile file, Authentication a
4847
Path tempFile = java.nio.file.Files.createTempFile("mod", ".tmp");
4948
file.transferTo(tempFile.getFileName().toFile());
5049

51-
modService.processUploadedMod(tempFile, playerService.getPlayer(authentication));
50+
modService.processUploadedMod(tempFile, playerService.getCurrentPlayer());
5251
}
5352
}

0 commit comments

Comments
 (0)