Skip to content

Commit fba6a91

Browse files
committed
Refactor clan creation to be done via Elide POST call
+ other cleanups
1 parent b02e3f3 commit fba6a91

File tree

13 files changed

+331
-216
lines changed

13 files changed

+331
-216
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ before_install:
2525
install:
2626
- git clone https://github.com/FAForever/faf-stack.git faf-stack
2727
&& pushd faf-stack
28-
&& git checkout 79c5d9d
28+
&& git checkout 13687d9
2929
&& cp -r config.template config
3030
&& cp .env.template .env
3131
&& ./scripts/init-db.sh

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: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,75 @@
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.transaction.annotation.Transactional;
21+
import org.springframework.util.Assert;
2322

2423
import java.time.Instant;
2524
import java.time.temporal.ChronoUnit;
2625
import java.util.Collections;
26+
import java.util.List;
2727

2828
@Service
2929
@RequiredArgsConstructor
3030
public class ClanService {
3131

3232
private final ClanRepository clanRepository;
33-
private final PlayerRepository playerRepository;
3433
private final FafApiProperties fafApiProperties;
3534
private final JwtService jwtService;
3635
private final ObjectMapper objectMapper;
3736
private final PlayerService playerService;
3837
private final ClanMembershipRepository clanMembershipRepository;
3938

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

5284
Clan clan = new Clan();
@@ -69,16 +101,18 @@ Clan create(String name, String tag, String description, Player creator) {
69101
}
70102

71103
@SneakyThrows
72-
String generatePlayerInvitationToken(Player requester, int newMemberId, int clanId) {
104+
@Transactional
105+
String generatePlayerInvitationToken(int newMemberId, int clanId) {
106+
Player requester = playerService.getCurrentPlayer();
107+
73108
Clan clan = clanRepository.findById(clanId)
74109
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
75110

76111
if (requester.getId() != clan.getLeader().getId()) {
77-
throw new ApiException(new Error(ErrorCode.CLAN_NOT_LEADER, clanId));
112+
throw ApiException.of(ErrorCode.CLAN_NOT_LEADER, clanId);
78113
}
79114

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

83117
long expire = Instant.now()
84118
.plus(fafApiProperties.getClan().getInviteLinkExpireDurationMinutes(), ChronoUnit.MINUTES)
@@ -91,28 +125,27 @@ String generatePlayerInvitationToken(Player requester, int newMemberId, int clan
91125
}
92126

93127
@SneakyThrows
94-
void acceptPlayerInvitationToken(String stringToken, Authentication authentication) {
128+
@Transactional
129+
void acceptPlayerInvitationToken(String stringToken) {
95130
Jwt token = jwtService.decodeAndVerify(stringToken);
96131
InvitationResult invitation = objectMapper.readValue(token.getClaims(), InvitationResult.class);
97132

98133
if (invitation.isExpired()) {
99-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE));
134+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE);
100135
}
101136

102137
final Integer clanId = invitation.getClan().getId();
103-
Player player = playerService.getPlayer(authentication);
138+
Player player = playerService.getCurrentPlayer();
104139
Clan clan = clanRepository.findById(clanId)
105140
.orElseThrow(() -> new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS, clanId)));
106141

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

111144
if (player.getId() != newMember.getId()) {
112-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER));
145+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER);
113146
}
114147
if (newMember.getClan() != null) {
115-
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN));
148+
throw ApiException.of(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN);
116149
}
117150

118151
ClanMembership membership = new ClanMembership();

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

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,21 @@
66
import com.faforever.api.data.domain.Clan;
77
import com.faforever.api.data.domain.Player;
88
import com.faforever.api.player.PlayerService;
9-
import com.google.common.collect.ImmutableMap;
109
import io.swagger.annotations.ApiOperation;
1110
import io.swagger.annotations.ApiResponse;
1211
import io.swagger.annotations.ApiResponses;
1312
import lombok.RequiredArgsConstructor;
1413
import org.springframework.security.access.prepost.PreAuthorize;
15-
import org.springframework.security.core.Authentication;
16-
import org.springframework.transaction.annotation.Transactional;
14+
import org.springframework.web.bind.annotation.GetMapping;
15+
import org.springframework.web.bind.annotation.PostMapping;
1716
import org.springframework.web.bind.annotation.RequestMapping;
18-
import org.springframework.web.bind.annotation.RequestMethod;
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

26-
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
23+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
2724

2825

2926
@RestController
@@ -37,11 +34,12 @@ public class ClansController {
3734

3835
@ApiOperation("Grab data about yourself and the clan")
3936
@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);
37+
@ApiResponse(code = 200, message = "Success with JSON { player: {id: ?, login: ?}, clan: { id: ?, name: ?, tag: ?}}"),
38+
@ApiResponse(code = 400, message = "Bad Request")})
39+
@GetMapping(path = "/me", produces = APPLICATION_JSON_VALUE)
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;
@@ -55,44 +53,33 @@ public MeResult me(Authentication authentication) {
5553
// a: the new clan with the leader membership, b: the leader membership with the new clan
5654
@ApiOperation("Create a clan with correct leader, founder and clan membership")
5755
@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)
56+
@ApiResponse(code = 200, message = "Success with JSON { id: ?, type: 'clan'}"),
57+
@ApiResponse(code = 400, message = "Bad Request")})
58+
@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);
69-
return ImmutableMap.of("id", clan.getId(), "type", "clan");
63+
@RequestParam(value = "description", required = false) String description) {
64+
Clan clan = clanService.create(name, tag, description);
65+
return Map.of("id", clan.getId(), "type", "clan");
7066
}
7167

7268
@ApiOperation("Generate invitation link")
7369
@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)
70+
@ApiResponse(code = 200, message = "Success with JSON { jwtToken: ? }"),
71+
@ApiResponse(code = 400, message = "Bad Request")})
72+
@GetMapping(path = "/generateInvitationLink", produces = APPLICATION_JSON_VALUE)
7973
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);
85-
return ImmutableMap.of("jwtToken", jwtToken);
74+
@RequestParam(value = "clanId") int clanId,
75+
@RequestParam(value = "playerId") int newMemberId) {
76+
String jwtToken = clanService.generatePlayerInvitationToken(newMemberId, clanId);
77+
return Map.of("jwtToken", jwtToken);
8678
}
8779

88-
@ApiOperation("Check invitation link and add Member to Clan")
89-
@RequestMapping(path = "/joinClan",
90-
method = RequestMethod.POST,
91-
produces = APPLICATION_JSON_UTF8_VALUE)
92-
@Transactional
93-
public void joinClan(
94-
@RequestParam(value = "token") String stringToken,
95-
Authentication authentication) throws IOException {
96-
clanService.acceptPlayerInvitationToken(stringToken, authentication);
80+
@ApiOperation("Check invitation link and add member to Clan")
81+
@PostMapping(path = "/joinClan", produces = APPLICATION_JSON_VALUE)
82+
public void joinClan(@RequestParam(value = "token") String stringToken) {
83+
clanService.acceptPlayerInvitationToken(stringToken);
9784
}
9885
}

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 = Boolean.TRUE;
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 founder 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)