Skip to content

Commit 111383e

Browse files
Add support for map&mod licenses (FAForever#703)
Co-authored-by: kubo <jsodpad@gmail.com>
1 parent f175f2f commit 111383e

26 files changed

Lines changed: 507 additions & 78 deletions

‎src/inttest/java/com/faforever/api/config/MainDbTestContainers.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
@Configuration
2222
public class MainDbTestContainers {
2323
private static final MariaDBContainer<?> fafDBContainer = new MariaDBContainer<>("mariadb:10.6");
24-
private static final GenericContainer<?> flywayMigrationsContainer = new GenericContainer<>("faforever/faf-db-migrations:v126");
24+
private static final GenericContainer<?> flywayMigrationsContainer = new GenericContainer<>("faforever/faf-db-migrations:v127");
2525
private static final Network sharedNetwork = Network.newNetwork();
2626

2727
@Bean

‎src/inttest/java/com/faforever/api/map/MapsControllerTest.java‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,19 @@ void jsonMetaDataMissing() throws Exception {
5858
mockMvc.perform(multipart("/maps/upload")
5959
.file(file)
6060
.with(getOAuthTokenWithActiveUser(OAuthScope._UPLOAD_MAP, NO_AUTHORITIES)))
61-
.andExpect(status().isBadRequest())
61+
.andExpect(status().isUnprocessableEntity())
6262
.andExpect(jsonPath("$.errors", hasSize(1)))
63-
.andExpect(jsonPath("$.errors[0].title", is("org.springframework.web.bind.MissingServletRequestParameterException")))
64-
.andExpect(jsonPath("$.errors[0].detail", is("Required request parameter 'metadata' for method parameter type String is not present")));
63+
.andExpect(jsonPath("$.errors[0].title", is("Missing parameter")))
64+
.andExpect(jsonPath("$.errors[0].detail", is("A parameter 'metadata' has to be provided.")));
6565
}
6666

6767
@Test
68-
void successUpload() throws Exception {
69-
String jsonString = "{}";
68+
void successUploadViaJsonRequestParam() throws Exception {
69+
String jsonString = """
70+
{
71+
"isRanked": true
72+
}
73+
""";
7074

7175
String zipFile = "command_conquer_rush.v0007.zip";
7276
try (InputStream inputStream = loadMapResourceAsStream(zipFile)) {
@@ -85,6 +89,33 @@ void successUpload() throws Exception {
8589
}
8690
}
8791

92+
@Test
93+
void successUploadViaMultipartJsonMetadata() throws Exception {
94+
String metadataString = """
95+
{
96+
"isRanked": true,
97+
"licenseId": 1
98+
}
99+
""";
100+
MockMultipartFile metadata = new MockMultipartFile("metadata", null, "application/json", metadataString.getBytes());
101+
102+
String zipFile = "command_conquer_rush.v0007.zip";
103+
try (InputStream inputStream = loadMapResourceAsStream(zipFile)) {
104+
MockMultipartFile file = new MockMultipartFile("file",
105+
zipFile,
106+
"application/zip",
107+
ByteStreams.toByteArray(inputStream));
108+
109+
mockMvc.perform(multipart("/maps/upload")
110+
.file(file)
111+
.file(metadata)
112+
.with(getOAuthTokenWithActiveUser(OAuthScope._UPLOAD_MAP, NO_AUTHORITIES))
113+
).andExpect(status().isOk());
114+
} finally {
115+
FileUtils.deleteDirectory(fafApiProperties.getMap().getTargetDirectory().toFile());
116+
}
117+
}
118+
88119
private InputStream loadMapResourceAsStream(String filename) {
89120
return MapsControllerTest.class.getResourceAsStream("/maps/" + filename);
90121
}

‎src/inttest/java/com/faforever/api/mod/ModsControllerTest.java‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,38 @@ void successUpload() throws Exception {
4646
"application/zip",
4747
ByteStreams.toByteArray(inputStream));
4848

49+
String metadataString = """
50+
{
51+
"licenseId": 1
52+
}
53+
""";
54+
MockMultipartFile metadata = new MockMultipartFile("metadata", null, "application/json", metadataString.getBytes());
55+
4956
mockMvc.perform(multipart("/mods/upload")
5057
.file(file)
58+
.file(metadata)
5159
.with(getOAuthTokenWithActiveUser(OAuthScope._UPLOAD_MOD, NO_AUTHORITIES))
5260
).andExpect(status().isOk());
5361
} finally {
5462
FileUtils.deleteDirectory(apiProperties.getMod().getTargetDirectory().toFile());
5563
}
5664
}
5765

66+
@Test
67+
void missingFilePartUnsuccessfulUpload() throws Exception {
68+
String metadataString = """
69+
{
70+
"licenseId": 1
71+
}
72+
""";
73+
MockMultipartFile metadata = new MockMultipartFile("metadata", null, "application/json", metadataString.getBytes());
74+
75+
mockMvc.perform(multipart("/mods/upload")
76+
.file(metadata)
77+
.with(getOAuthTokenWithActiveUser(OAuthScope._UPLOAD_MOD, NO_AUTHORITIES))
78+
).andExpect(status().isBadRequest());
79+
}
80+
5881
private InputStream loadModResourceAsStream(String filename) {
5982
return ModsControllerTest.class.getResourceAsStream("/mods/" + filename);
6083
}

‎src/inttest/resources/config/application.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ faf-api:
4646
small-previews-url-format: "http://localhost/faf/vault/map_previews/small/%s"
4747
large-previews-url-format: "http://localhost/faf/vault/map_previews/large/%s"
4848
download-url-format: "http://localhost/faf/vault/maps/%s"
49+
default-license-id: 1
4950
avatar:
5051
download-url-format: "http://localhost/faf/avatars/%s"
5152
target-directory: "build/cache/avatars"
@@ -68,6 +69,7 @@ faf-api:
6869
mod:
6970
download-url-format: "http://localhost/mod/download-url/%s"
7071
preview-url-format: "http://localhost/mod/preview-url/%s"
72+
default-license-id: 1
7173
password-reset:
7274
password-reset-url-format: "http://localhost:8010/users/claimPasswordResetToken/%s"
7375
subject: "Integration test password reset"

‎src/inttest/resources/sql/prepMapData.sql‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
INSERT INTO map (id, display_name, map_type, battle_type, author) VALUES
2-
(1, 'SCMP_001', 'FFA', 'skirmish', 1),
3-
(2, 'SCMP_002', 'FFA', 'skirmish', 1);
1+
INSERT INTO map (id, display_name, map_type, battle_type, author, license) VALUES
2+
(1, 'SCMP_001', 'FFA', 'skirmish', 1, 1),
3+
(2, 'SCMP_002', 'FFA', 'skirmish', 1, 1);
44

55
INSERT INTO map_version (id, description, max_players, width, height, version, filename, hidden, map_id) VALUES
66
(1, 'SCMP 001', 8, 5, 5, 1, 'maps/scmp_001.v0001.zip', 0, 1),
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
INSERT INTO map (id, display_name, map_type, battle_type, author, recommended) VALUES (1, 'display name', 'mtype', 'btype', 1, false);
1+
INSERT INTO map (id, display_name, map_type, battle_type, author, recommended, license) VALUES (1, 'display name', 'mtype', 'btype', 1, false, 1);
22
INSERT INTO map_version (id, description, max_players, width, height, version, filename, map_id, hidden, ranked)
33
VALUES (1, 'des', 2, 2, 2, 1, 'map/ghb.zip', 1, 0, 1);

‎src/inttest/resources/sql/prepModData.sql‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
INSERT INTO `mod` (id, display_name, author, uploader, recommended)
2-
VALUES (1, 'MOD_001', 'some Author', 1, false),
3-
(2, 'MOD_002', 'some Author', 1, false);
1+
INSERT INTO `mod` (id, display_name, author, uploader, recommended, license)
2+
VALUES (1, 'MOD_001', 'some Author', 1, false, 1),
3+
(2, 'MOD_002', 'some Author', 1, false, 1);
44

55
INSERT INTO mod_version (id, description, type, uid, version, filename, hidden, mod_id)
66
VALUES (1, 'SCMP 001', 'SIM', '11111111-1111-1111-1111-111111111111', 1, 'maps/scmp_001.v0001.zip', false, 1),

‎src/main/java/com/faforever/api/config/FafApiProperties.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.faforever.api.config;
22

33
import lombok.Data;
4+
import org.checkerframework.checker.index.qual.Positive;
45
import org.springframework.boot.context.properties.ConfigurationProperties;
56

67
import java.nio.file.Path;
@@ -93,15 +94,22 @@ public static class Map {
9394
/**
9495
* The size (in pixels) of small map previews.
9596
*/
97+
@Positive
9698
private int previewSizeSmall = 128;
9799
/**
98100
* The size (in pixels) of large map previews.
99101
*/
102+
@Positive
100103
private int previewSizeLarge = 512;
101104
/**
102105
* Allowed file extensions of uploaded maps.
103106
*/
104107
private Set<String> allowedExtensions = Set.of("zip");
108+
/**
109+
* The default license if no license is provided on upload
110+
*/
111+
@Positive
112+
private int defaultLicenseId;
105113
}
106114

107115
@Data
@@ -115,9 +123,16 @@ public static class Mod {
115123
/** The directory in which thumbnails of uploaded mod files are stored. */
116124
private Path thumbnailTargetDirectory = Paths.get("static/mod_thumbnails");
117125
/** The maximum allowed length of a mod's name. */
126+
@Positive
118127
private int maxNameLength = 100;
119128
/** The minimum allowed length of a mod's name. */
129+
@Positive
120130
private int minNameLength = 3;
131+
/**
132+
* The default license if no license is provided on upload
133+
*/
134+
@Positive
135+
private int defaultLicenseId;
121136
}
122137

123138
@Data
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.faforever.api.config;
2+
3+
import com.faforever.api.map.MapUploadMetadata;
4+
import org.springframework.http.HttpInputMessage;
5+
import org.springframework.http.HttpOutputMessage;
6+
import org.springframework.http.MediaType;
7+
import org.springframework.http.converter.AbstractHttpMessageConverter;
8+
import org.springframework.lang.Nullable;
9+
import org.springframework.security.core.Authentication;
10+
import org.springframework.util.StreamUtils;
11+
import org.springframework.web.multipart.MultipartFile;
12+
13+
import java.io.IOException;
14+
15+
/**
16+
* This class is used to support having both @RequestParam and @RequestPart with same multipart name in one request handler.
17+
* When multipart request contains simple request param octet-stream, this class is used to ignore parsing
18+
* of byte stream to {@link MapUploadMetadata}.
19+
* See {@link com.faforever.api.map.MapsController#uploadMap(MultipartFile, String, MapUploadMetadata, Authentication)}
20+
*/
21+
public class IgnoreOctetStreamToObjectHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {
22+
23+
24+
public IgnoreOctetStreamToObjectHttpMessageConverter() {
25+
super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
26+
}
27+
28+
29+
@Override
30+
public boolean supports(Class<?> clazz) {
31+
return MapUploadMetadata.class == clazz;
32+
}
33+
34+
@Override
35+
public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage message) throws IOException {
36+
return null;
37+
}
38+
39+
@Override
40+
protected Long getContentLength(byte[] bytes, @Nullable MediaType contentType) {
41+
return 0L;
42+
}
43+
44+
@Override
45+
protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
46+
StreamUtils.copy(bytes, outputMessage.getBody());
47+
}
48+
}

‎src/main/java/com/faforever/api/config/MvcConfig.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.springframework.context.annotation.Configuration;
44
import org.springframework.core.Ordered;
5+
import org.springframework.format.FormatterRegistry;
6+
import org.springframework.http.converter.HttpMessageConverter;
57
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
68
import org.springframework.web.servlet.config.annotation.CorsRegistry;
79
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -10,6 +12,8 @@
1012
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
1113
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
1214

15+
import java.util.List;
16+
1317
@EnableWebMvc
1418
@Configuration
1519
public class MvcConfig implements WebMvcConfigurer {
@@ -43,4 +47,14 @@ public void addCorsMappings(CorsRegistry registry) {
4347
registry.addMapping("/**")
4448
.allowedMethods("*");
4549
}
50+
51+
@Override
52+
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
53+
converters.add(new IgnoreOctetStreamToObjectHttpMessageConverter());
54+
}
55+
56+
@Override
57+
public void addFormatters(FormatterRegistry registry) {
58+
registry.addConverter(new NoopMultipartFileToStringConverter());
59+
}
4660
}

0 commit comments

Comments
 (0)