Added SteamAPI and achievement searching

This commit is contained in:
Gnarwhal 2021-02-18 02:15:09 -05:00
parent b229ff9a15
commit 627cc810ed
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
61 changed files with 2781 additions and 903 deletions

View file

@ -4,11 +4,15 @@ import achievements.misc.DbConnection;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.Duration;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@EnableScheduling
public class Application {
@ -34,4 +38,9 @@ public class Application {
}
};
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View file

@ -0,0 +1,17 @@
package achievements.apis;
import achievements.data.APIResponse;
import org.springframework.web.client.RestTemplate;
public abstract class PlatformAPI {
protected int id;
protected RestTemplate rest;
protected PlatformAPI(int id, RestTemplate rest) {
this.id = id;
this.rest = rest;
}
public abstract APIResponse get(String userId);
}

View file

@ -0,0 +1,102 @@
package achievements.apis;
import achievements.apis.steam.GetOwnedGameBody;
import achievements.apis.steam.GetPlayerAchievementsBody;
import achievements.apis.steam.GetSchemaForGameBody;
import achievements.data.APIResponse;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
public class SteamAPI extends PlatformAPI {
private String apiKey;
public SteamAPI(int id, RestTemplate rest) {
super(id, rest);
try {
var file = new FileInputStream("storage/apis/" + id + ".properties");
var properties = new Properties();
properties.load(file);
apiKey = properties.getProperty("api-key");
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public APIResponse get(String userId) {
var headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
var entity = new HttpEntity<String>(headers);
var ownedGamesUrl = UriComponentsBuilder.fromHttpUrl("http://api.steampowered.com/IPlayerService/GetOwnedGames/v1/")
.queryParam("key", apiKey)
.queryParam("steamid", userId)
.queryParam("include_appinfo", true)
.queryParam("include_played_free_games", true)
.toUriString();
var gameSchemaBaseUrl = UriComponentsBuilder.fromHttpUrl("https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/")
.queryParam("key", apiKey);
var playerAchievementsBaseUrl = UriComponentsBuilder.fromHttpUrl("https://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v1/")
.queryParam("key", apiKey)
.queryParam("steamid", userId);
var games = new ArrayList<APIResponse.Game>();
var ownedResponse = rest.exchange(ownedGamesUrl, HttpMethod.GET, entity, GetOwnedGameBody.class).getBody();
for (var game : ownedResponse.getResponse().getGames()) {
var newGame = new APIResponse.Game();
newGame.setPlatformGameId(Integer.toString(game.getAppid()));
newGame.setName(game.getName());
newGame.setThumbnail("https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/" + game.getAppid() + "/" + game.getImg_logo_url() + ".jpg");
newGame.setPlayed(game.getPlaytime_forever() > 0);
var achievements = new HashMap<String, APIResponse.Game.Achievement>();
var gameSchemaUrl = gameSchemaBaseUrl.cloneBuilder()
.queryParam("appid", game.getAppid())
.toUriString();
var playerAchievementsUrl = playerAchievementsBaseUrl.cloneBuilder()
.queryParam("appid", game.getAppid())
.toUriString();
var schemaResponse = rest.exchange(gameSchemaUrl, HttpMethod.GET, entity, GetSchemaForGameBody.class).getBody().getGame().getAvailableGameStats();
if (schemaResponse != null && schemaResponse.getAchievements() != null) {
for (var schema : schemaResponse.getAchievements()) {
var achievement = new APIResponse.Game.Achievement();
achievement.setName(schema.getDisplayName());
achievement.setDescription(schema.getDescription());
achievement.setStages(1);
achievement.setThumbnail(schema.getIcon());
achievements.put(schema.getName(), achievement);
}
var playerAchievementsResponse = rest.exchange(playerAchievementsUrl, HttpMethod.GET, entity, GetPlayerAchievementsBody.class).getBody().getPlayerstats().getAchievements();
for (var achievement : playerAchievementsResponse) {
achievements.get(achievement.getApiname()).setProgress(achievement.getAchieved());
}
newGame.setAchievements(new ArrayList<>(achievements.values()));
if (newGame.getAchievements().size() > 0) {
games.add(newGame);
}
}
}
var response = new APIResponse();
response.setGames(games);
return response;
}
}

View file

@ -0,0 +1,96 @@
package achievements.apis.steam;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class GetOwnedGameBody {
public static class Response {
public static class Game {
@JsonProperty("appid")
private int appid;
@JsonProperty("name")
private String name;
@JsonProperty("playtime_forever")
private int playtime_forever;
@JsonProperty("img_icon_url")
private String img_icon_url;
@JsonProperty("img_logo_url")
private String img_logo_url;
public int getAppid() {
return appid;
}
public void setAppid(int appid) {
this.appid = appid;
}
public int getPlaytime_forever() {
return playtime_forever;
}
public void setPlaytime_forever(int playtime_forever) {
this.playtime_forever = playtime_forever;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImg_icon_url() {
return img_icon_url;
}
public void setImg_icon_url(String img_icon_url) {
this.img_icon_url = img_icon_url;
}
public String getImg_logo_url() {
return img_logo_url;
}
public void setImg_logo_url(String img_logo_url) {
this.img_logo_url = img_logo_url;
}
}
@JsonProperty("game_count")
private int game_count;
@JsonProperty("games")
private List<Game> games;
public int getGame_count() {
return game_count;
}
public void setGame_count(int game_count) {
this.game_count = game_count;
}
public List<Game> getGames() {
return games;
}
public void setGames(List<Game> games) {
this.games = games;
}
}
@JsonProperty("response")
private Response response;
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
}

View file

@ -0,0 +1,54 @@
package achievements.apis.steam;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class GetPlayerAchievementsBody {
public static class PlayerStats {
public static class Achievement {
@JsonProperty("apiname")
private String apiname;
@JsonProperty("achieved")
private int achieved;
public String getApiname() {
return apiname;
}
public void setApiname(String apiname) {
this.apiname = apiname;
}
public int getAchieved() {
return achieved;
}
public void setAchieved(int achieved) {
this.achieved = achieved;
}
}
@JsonProperty("achievements")
private List<Achievement> achievements;
public List<Achievement> getAchievements() {
return achievements;
}
public void setAchievements(List<Achievement> achievements) {
this.achievements = achievements;
}
}
@JsonProperty("playerstats")
private PlayerStats playerstats;
public PlayerStats getPlayerstats() {
return playerstats;
}
public void setPlayerstats(PlayerStats playerstats) {
this.playerstats = playerstats;
}
}

View file

@ -0,0 +1,87 @@
package achievements.apis.steam;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class GetSchemaForGameBody {
public static class Game {
public static class GameStats {
public static class Achievement {
@JsonProperty("name")
private String name;
@JsonProperty("displayName")
private String displayName;
@JsonProperty("description")
private String description;
@JsonProperty("icon")
private String icon;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
}
@JsonProperty("achievements")
private List<Achievement> achievements;
public List<Achievement> getAchievements() {
return achievements;
}
public void setAchievements(List<Achievement> achievements) {
this.achievements = achievements;
}
}
@JsonProperty("availableGameStats")
private GameStats availableGameStats;
public GameStats getAvailableGameStats() {
return availableGameStats;
}
public void setAvailableGameStats(GameStats availableGameStats) {
this.availableGameStats = availableGameStats;
}
}
@JsonProperty("game")
private Game game;
public Game getGame() {
return game;
}
public void setGame(Game game) {
this.game = game;
}
}

View file

@ -0,0 +1,27 @@
package achievements.controllers;
import achievements.services.ImageService;
import achievements.services.AchievementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/achievement")
public class AchievementController {
@Autowired
private AchievementService achievementService;
@Autowired
private ImageService imageService;
@GetMapping(value = "/{achievement}/image")
public void getProfilePicture(@PathVariable("achievement") int achievement, HttpServletResponse response) {
var icon = achievementService.getIcon(achievement);
imageService.send(icon, "achievement", response);
}
}

View file

@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/auth")
public class LoginController {
public class AuthController {
@Autowired
private AuthenticationService authService;

View file

@ -0,0 +1,27 @@
package achievements.controllers;
import achievements.services.ImageService;
import achievements.services.GameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/game")
public class GameController {
@Autowired
private GameService gameService;
@Autowired
private ImageService imageService;
@GetMapping(value = "/{game}/image")
public void getProfilePicture(@PathVariable("game") int game, HttpServletResponse response) {
var icon = gameService.getIcon(game);
imageService.send(icon, "game", response);
}
}

View file

@ -1,42 +1,27 @@
package achievements.controllers;
import achievements.services.ImageService;
import achievements.services.PlatformService;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@RestController
@RequestMapping("/platform")
public class PlatformController {
@Autowired
private PlatformService platforms;
private ImageService imageService;
@Autowired
private PlatformService platformService;
@GetMapping(value = "/platform/image/{id}", produces = "application/json")
public void getPlatformImage(@PathVariable("id") int id, HttpServletResponse response) {
try {
var file = new File("images/platform/" + id + ".png");
if (file.exists()) {
var stream = new FileInputStream(file);
IOUtils.copy(stream, response.getOutputStream());
response.setContentType("image/png");
response.setStatus(200);
response.flushBuffer();
stream.close();
} else {
response.setStatus(HttpStatus.BAD_REQUEST.value());
}
} catch (IOException e) {
e.printStackTrace();
}
@GetMapping(value = "/{platform}/image")
public void getIcon(@PathVariable("platform") int platform, HttpServletResponse response) {
var icon = platformService.getIcon(platform);
imageService.send(icon, "platform", response);
}
}

View file

@ -0,0 +1,26 @@
package achievements.controllers;
import achievements.data.query.SearchAchievements;
import achievements.services.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SearchController {
@Autowired
private SearchService searchService;
@PostMapping(value = "/achievements", consumes = "application/json", produces = "application/json")
public ResponseEntity searchAchievements(@RequestBody SearchAchievements searchAchievements) {
var achievements = searchService.searchAchievements(searchAchievements);
if (achievements != null) {
return ResponseEntity.ok(achievements);
} else {
return ResponseEntity.badRequest().body("[]");
}
}
}

View file

@ -2,20 +2,18 @@ package achievements.controllers;
import achievements.data.APError;
import achievements.data.APPostRequest;
import achievements.data.query.AddPlatformRequest;
import achievements.data.query.RemovePlatformRequest;
import achievements.data.query.AddPlatform;
import achievements.data.query.RemovePlatform;
import achievements.data.query.SetUsername;
import achievements.services.ImageService;
import achievements.services.UserService;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@RestController
@RequestMapping("/user")
@ -24,6 +22,9 @@ public class UserController {
@Autowired
private UserService userService;
@Autowired
private ImageService imageService;
@GetMapping(value = "/{user}", produces = "application/json")
public ResponseEntity getProfile(@PathVariable("user") int user) {
var profile = userService.getProfile(user);
@ -45,38 +46,21 @@ public class UserController {
@GetMapping(value = "/{user}/image")
public void getProfilePicture(@PathVariable("user") int user, HttpServletResponse response) {
var pfp = userService.getProfileImageType(user);
if (pfp == null) {
} else {
var file = new File("images/user/" + pfp[0] + "." + pfp[1]);
response.setContentType("image/" + pfp[2]);
try {
var stream = new FileInputStream(file);
IOUtils.copy(stream, response.getOutputStream());
response.flushBuffer();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
var profileImage = userService.getProfileImage(user);
imageService.send(profileImage, "user", response);
}
@PostMapping(value = "/{user}/image", consumes = "multipart/form-data", produces = "application/json")
public ResponseEntity setProfilePicture(@PathVariable("user") int user, @RequestPart APPostRequest session, @RequestPart MultipartFile file) {
try {
var type = userService.setProfileImageType(user, session.getKey(), file.getContentType());
var type = userService.setProfileImage(user, session.getKey(), file);
if ("not_an_image".equals(type)) {
return ResponseEntity.badRequest().body("{ \"code\": 1, \"message\": \"Not an image type\" }");
} else if ("unsupported_type".equals(type)) {
return ResponseEntity.badRequest().body("{ \"code\": 1, \"message\": \"Unsupported file type\" }");
} else if ("forbidden".equals(type)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("{ \"code\": 2, \"message\": \"Invalid credentials\" }");
} else if (!"unknown".equals(type)) {
var pfp = new FileOutputStream("images/user/" + user + "." + type);
FileCopyUtils.copy(file.getInputStream(), pfp);
pfp.close();
} else if ("success".equals(type)) {
return ResponseEntity.status(HttpStatus.CREATED).body("{ \"code\": 0, \"message\": \"Success\" }");
}
@ -87,7 +71,7 @@ public class UserController {
}
@PostMapping(value = "/{user}/platforms/add", consumes = "application/json", produces = "application/json")
public ResponseEntity addPlatformForUser(@PathVariable("user") int userId, @RequestBody AddPlatformRequest request) {
public ResponseEntity addPlatformForUser(@PathVariable("user") int userId, @RequestBody AddPlatform request) {
var result = userService.addPlatform(userId, request);
if (result == 0) {
return ResponseEntity.status(HttpStatus.CREATED).body("{}");
@ -97,7 +81,7 @@ public class UserController {
}
@PostMapping(value = "/{user}/platforms/remove", consumes = "application/json", produces = "application/json")
public ResponseEntity removePlatformForUser(@PathVariable("user") int userId, @RequestBody RemovePlatformRequest request) {
public ResponseEntity removePlatformForUser(@PathVariable("user") int userId, @RequestBody RemovePlatform request) {
var result = userService.removePlatform(userId, request);
if (result == 0) {
return ResponseEntity.status(HttpStatus.CREATED).body("{}");

View file

@ -0,0 +1,126 @@
package achievements.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class APIResponse {
public static class Game {
public static class Achievement {
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("stages")
private int stages;
@JsonProperty("progress")
private int progress;
@JsonProperty("thumbnail")
private String thumbnail;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getStages() {
return stages;
}
public void setStages(int stages) {
this.stages = stages;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
}
@JsonProperty("platformGameId")
private String platformGameId;
@JsonProperty("name")
private String name;
@JsonProperty("played")
private boolean played;
@JsonProperty("thumbnail")
private String thumbnail;
@JsonProperty("achievements")
private List<Achievement> achievements;
public String getPlatformGameId() {
return platformGameId;
}
public void setPlatformGameId(String platformGameId) {
this.platformGameId = platformGameId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isPlayed() {
return played;
}
public void setPlayed(boolean played) {
this.played = played;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
public List<Achievement> getAchievements() {
return achievements;
}
public void setAchievements(List<Achievement> achievements) {
this.achievements = achievements;
}
}
@JsonProperty("games")
private List<Game> games;
public List<Game> getGames() {
return games;
}
public void setGames(List<Game> games) {
this.games = games;
}
}

View file

@ -1,122 +1,38 @@
package achievements.data;
import achievements.data.query.NumericFilter;
import achievements.data.query.StringFilter;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Achievement {
public static class Query {
@JsonProperty("sessionKey")
private String sessionKey;
@JsonProperty("name")
private StringFilter name;
@JsonProperty("stages")
private NumericFilter stages;
@JsonProperty("completion")
private NumericFilter completion;
@JsonProperty("difficulty")
private NumericFilter difficulty;
@JsonProperty("quality")
private NumericFilter quality;
public Query(String sessionKey, StringFilter name, NumericFilter stages, NumericFilter completion, NumericFilter difficulty, NumericFilter quality) {
this.sessionKey = sessionKey;
this.name = name;
this.stages = stages;
this.completion = completion;
this.difficulty = difficulty;
this.quality = quality;
}
public String getSessionKey() {
return sessionKey;
}
public void setSessionKey(String sessionKey) {
this.sessionKey = sessionKey;
}
public StringFilter getName() {
return name;
}
public void setName(StringFilter name) {
this.name = name;
}
public NumericFilter getStages() {
return stages;
}
public void setStages(NumericFilter stages) {
this.stages = stages;
}
public NumericFilter getCompletion() {
return completion;
}
public void setCompletion(NumericFilter completion) {
this.completion = completion;
}
public NumericFilter getDifficulty() {
return difficulty;
}
public void setDifficulty(NumericFilter difficulty) {
this.difficulty = difficulty;
}
public NumericFilter getQuality() {
return quality;
}
public void setQuality(NumericFilter quality) {
this.quality = quality;
}
}
@JsonProperty("ID")
private int id;
private int ID;
@JsonProperty("game")
private int gameId;
private String game;
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("stages")
private int stages;
@JsonProperty("completion")
private float completion;
private Integer completion;
@JsonProperty("difficulty")
private float difficulty;
private Float difficulty;
@JsonProperty("quality")
private float quality;
private Float quality;
public Achievement(int id, int gameId, String name, String description, int stages, float completion, float difficulty, float quality) {
this.id = id;
this.gameId = gameId;
this.name = name;
this.description = description;
this.stages = stages;
this.completion = completion;
this.difficulty = difficulty;
this.quality = quality;
public int getID() {
return ID;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public int getGameId() {
return gameId;
public void setID(int ID) {
this.ID = ID;
}
public void setGameId(int gameId) {
this.gameId = gameId;
public String getGame() {
return game;
}
public void setGame(String game) {
this.game = game;
}
public String getName() { return name; }
@ -127,31 +43,27 @@ public class Achievement {
public void setDescription(String description) { this.description = description; }
public int getStages() { return stages; }
public void setStages(int stages) { this.stages = stages; }
public float getCompletion() {
public Integer getCompletion() {
return completion;
}
public void setCompletion(float completion) {
public void setCompletion(Integer completion) {
this.completion = completion;
}
public float getDifficulty() {
public Float getDifficulty() {
return difficulty;
}
public void setDifficulty(float difficulty) {
public void setDifficulty(Float difficulty) {
this.difficulty = difficulty;
}
public float getQuality() {
public Float getQuality() {
return quality;
}
public void setQuality(float quality) {
public void setQuality(Float quality) {
this.quality = quality;
}
}

View file

@ -1,20 +1,11 @@
package achievements.data;
import achievements.data.query.StringFilter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
public class Game {
public static class Query {
@JsonProperty("name")
private StringFilter name;
@JsonProperty("platforms")
private StringFilter platforms;
}
@JsonProperty("ID")
private int id;
@JsonProperty("name")
@ -24,13 +15,6 @@ public class Game {
@JsonProperty("achievementCount")
private int achievementCount;
public Game(int id, String name, String platform) {
this.id = id;
this.name = name;
this.platforms = new ArrayList<>();
this.platforms.add(platform);
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }

View file

@ -1,6 +1,5 @@
package achievements.data;
import achievements.data.query.StringFilter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

View file

@ -11,14 +11,17 @@ public class Session {
private int id;
@JsonProperty("hue")
private int hue;
@JsonProperty("admin")
private boolean admin;
@JsonIgnore
private boolean used;
public Session(String key, int id, int hue) {
this.key = key;
this.id = id;
this.hue = hue;
this.used = false;
public Session(String key, int id, int hue, boolean admin) {
this.key = key;
this.id = id;
this.hue = hue;
this.admin = admin;
this.used = false;
}
public String getKey() {
@ -45,7 +48,15 @@ public class Session {
this.hue = hue;
}
public boolean getUsed() {
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public boolean isUsed() {
return used;
}

View file

@ -2,7 +2,7 @@ package achievements.data.query;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AddPlatformRequest {
public class AddPlatform {
@JsonProperty("sessionKey")
private String sessionKey;
@JsonProperty("platformId")

View file

@ -1,32 +0,0 @@
package achievements.data.query;
import com.fasterxml.jackson.annotation.JsonProperty;
public class NumericFilter {
@JsonProperty("min")
private Float min;
@JsonProperty("max")
private Float max;
public NumericFilter(Float min, Float max) {
this.min = min;
this.max = max;
}
public Float getMin() {
return min;
}
public void setMin(Float min) {
this.min = min;
}
public Float getMax() {
return max;
}
public void setMax(Float max) {
this.max = max;
}
}

View file

@ -2,7 +2,7 @@ package achievements.data.query;
import com.fasterxml.jackson.annotation.JsonProperty;
public class RemovePlatformRequest {
public class RemovePlatform {
@JsonProperty("sessionKey")
private String sessionKey;
@JsonProperty("platformId")

View file

@ -0,0 +1,97 @@
package achievements.data.query;
import com.fasterxml.jackson.annotation.JsonProperty;
public class SearchAchievements {
@JsonProperty("searchTerm")
private String searchTerm;
@JsonProperty("userId")
private Integer userId;
@JsonProperty("completed")
private boolean completed;
@JsonProperty("minCompletion")
private Float minCompletion;
@JsonProperty("maxCompletion")
private Float maxCompletion;
@JsonProperty("minDifficulty")
private Float minDifficulty;
@JsonProperty("maxDifficulty")
private Float maxDifficulty;
@JsonProperty("minQuality")
private Float minQuality;
@JsonProperty("maxQuality")
private Float maxQuality;
public String getSearchTerm() {
return searchTerm;
}
public void setSearchTerm(String searchTerm) {
this.searchTerm = searchTerm;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public Float getMinCompletion() {
return minCompletion;
}
public void setMinCompletion(Float minCompletion) {
this.minCompletion = minCompletion;
}
public Float getMaxCompletion() {
return maxCompletion;
}
public void setMaxCompletion(Float maxCompletion) {
this.maxCompletion = maxCompletion;
}
public Float getMinDifficulty() {
return minDifficulty;
}
public void setMinDifficulty(Float minDifficulty) {
this.minDifficulty = minDifficulty;
}
public Float getMaxDifficulty() {
return maxDifficulty;
}
public void setMaxDifficulty(Float maxDifficulty) {
this.maxDifficulty = maxDifficulty;
}
public Float getMinQuality() {
return minQuality;
}
public void setMinQuality(Float minQuality) {
this.minQuality = minQuality;
}
public Float getMaxQuality() {
return maxQuality;
}
public void setMaxQuality(Float maxQuality) {
this.maxQuality = maxQuality;
}
}

View file

@ -1,21 +0,0 @@
package achievements.data.query;
import com.fasterxml.jackson.annotation.JsonProperty;
public class StringFilter {
@JsonProperty("query")
private String query;
public StringFilter(String query) {
this.query = query;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
}

View file

@ -0,0 +1,41 @@
package achievements.misc;
import achievements.apis.PlatformAPI;
import achievements.apis.SteamAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Component
public class APIList {
@Autowired
private RestTemplate rest;
public final Map<Integer, PlatformAPI> apis = new HashMap<>();
@PostConstruct
private void init() {
/*db = dbs.getConnection();
try {
var stmt = db.prepareCall("{call GetPlatforms()}");
var results = stmt.executeQuery();
while (results.next()) {
var id = results.getInt("ID");
// Wanted to pull some skekery with dynamic class loading and external api jars, but...time is of the essence and I need to cut scope as much as possible
apis.put(id, new ????(id, rest));
}
} catch (Exception e) {
e.printStackTrace();
}*/
apis.put(0, new SteamAPI(0, rest));
}
}

View file

@ -13,12 +13,12 @@ public class SessionManager {
private HashMap<String, Session> sessions;
public SessionManager() {
sessions = new HashMap();
sessions = new HashMap<>();
}
public Session generate(int user, int hue) {
public Session generate(int user, int hue, boolean admin) {
var key = HashManager.encode(HashManager.generateBytes(16));
var session = new Session(key, user, hue);
var session = new Session(key, user, hue, admin);
sessions.put(key, session);
return session;
}
@ -32,8 +32,13 @@ public class SessionManager {
}
public boolean validate(int user, String key) {
var foreign = sessions.get(key);
return foreign != null && user == foreign.getId();
var session = sessions.get(key);
return session != null && user == session.getId();
}
public boolean validateAdmin(int user, String key) {
var session = sessions.get(key);
return session != null && user == session.getId() && session.isAdmin();
}
public boolean refresh(String key) {
@ -51,7 +56,7 @@ public class SessionManager {
public void clean() {
var remove = new ArrayList<String>();
sessions.forEach((key, session) -> {
if (!session.getUsed()) {
if (!session.isUsed()) {
remove.add(session.getKey());
} else {
session.setUsed(false);

View file

@ -0,0 +1,111 @@
package achievements.services;
import achievements.misc.APIList;
import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.Types;
@Service
public class APIService {
@Autowired
private RestTemplate rest;
@Autowired
private APIList apis;
@Autowired
private DbConnection dbs;
private Connection db;
@PostConstruct
private void init() {
db = dbs.getConnection();
}
private String getFileType(String imagePath) {
var path = imagePath.split("\\.");
return path[path.length - 1];
}
public int importUserPlatform(int userId, int platformId, String platformUserId) {
try {
var response = apis.apis.get(platformId).get(platformUserId);
var addIfNotGame = db.prepareCall("{call AddIfNotGame(?, ?, ?)}");
var addGameToPlatform = db.prepareCall("{call AddGameToPlatform(?, ?, ?)}");
var addGameToUser = db.prepareCall("{call AddGameToPlatform(?, ?, ?)}");
var addIfNotAchievement = db.prepareCall("{call AddIfNotAchievement(?, ?, ?, ?, ?, ?)}");
var setAchievementProgressForUser = db.prepareCall("{call SetAchievementProgressForUser(?, ?, ?, ?)}");
addIfNotGame.registerOutParameter(3, Types.INTEGER);
addIfNotAchievement.registerOutParameter(6, Types.INTEGER);
for (var game : response.getGames()) {
addIfNotGame.setString(1, game.getName());
addIfNotGame.setString(2, getFileType(game.getThumbnail()));
addIfNotGame.execute();
var gameId = addIfNotGame.getInt(3);
addGameToPlatform.setInt(1, gameId);
addGameToPlatform.setInt(2, platformId);
addGameToPlatform.setString(3, platformUserId);
addGameToPlatform.execute();
var gameThumbnail = new File("storage/images/game/" + gameId + "." + getFileType(game.getThumbnail()));
if (!gameThumbnail.exists()) {
var bytes = rest.getForObject(game.getThumbnail(), byte[].class);
var stream = new FileOutputStream(gameThumbnail);
stream.write(bytes);
stream.close();
}
addGameToUser.setInt(1, gameId);
addGameToUser.setInt(2, userId);
addGameToUser.setInt(3, platformId);
addGameToUser.execute();
for (var achievement : game.getAchievements()) {
addIfNotAchievement.setInt(1, gameId);
addIfNotAchievement.setString(2, achievement.getName());
addIfNotAchievement.setString(3, achievement.getDescription());
addIfNotAchievement.setInt(4, achievement.getStages());
addIfNotAchievement.setString(5, getFileType(achievement.getThumbnail()));
addIfNotAchievement.execute();
var achievementId = addIfNotAchievement.getInt(6);
var achievementIcon = new File("storage/images/achievement/" + achievementId + "." + getFileType(achievement.getThumbnail()));
if (!achievementIcon.exists()) {
var bytes = rest.getForObject(achievement.getThumbnail(), byte[].class);
var stream = new FileOutputStream(achievementIcon);
stream.write(bytes);
stream.close();
}
if (game.isPlayed()) {
setAchievementProgressForUser.setInt(1, userId);
setAchievementProgressForUser.setInt(2, platformId);
setAchievementProgressForUser.setInt(3, achievementId);
setAchievementProgressForUser.setInt(4, achievement.getProgress());
setAchievementProgressForUser.execute();
}
}
}
addIfNotGame.close();
addGameToPlatform.close();
addIfNotAchievement.close();
setAchievementProgressForUser.close();
return 0;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
}

View file

@ -0,0 +1,34 @@
package achievements.services;
import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.sql.Connection;
@Service
public class AchievementService {
@Autowired
private DbConnection dbs;
private Connection db;
@Autowired
private ImageService imageService;
@PostConstruct
private void init() {
db = dbs.getConnection();
}
public String[] getIcon(int achievementId) {
try {
var stmt = db.prepareCall("{call GetAchievementIcon(?)}");
return imageService.getImageType(stmt, achievementId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -68,7 +68,8 @@ public class AuthenticationService {
statement.getInt(1),
session.generate(
statement.getInt(6),
statement.getInt(7)
statement.getInt(7),
false
)
);
statement.close();
@ -95,7 +96,8 @@ public class AuthenticationService {
0,
session.generate(
result.getInt("ID"),
result.getInt("Hue")
result.getInt("Hue"),
result.getBoolean("Admin")
)
);
} else {

View file

@ -0,0 +1,34 @@
package achievements.services;
import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.sql.Connection;
@Service
public class GameService {
@Autowired
private DbConnection dbs;
private Connection db;
@Autowired
private ImageService imageService;
@PostConstruct
private void init() {
db = dbs.getConnection();
}
public String[] getIcon(int gameId) {
try {
var stmt = db.prepareCall("{call GetAchievementIcon(?)}");
return imageService.getImageType(stmt, gameId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -0,0 +1,80 @@
package achievements.services;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.HashMap;
@Service
public class ImageService {
public static final HashMap<String, String> MIME_TO_EXT = new HashMap<>();
public static final HashMap<String, String> EXT_TO_MIME = new HashMap<>();
static {
MIME_TO_EXT.put("apng", "apng");
MIME_TO_EXT.put("avif", "avif");
MIME_TO_EXT.put("gif", "gif" );
MIME_TO_EXT.put("jpeg", "jpg" );
MIME_TO_EXT.put("png", "png" );
MIME_TO_EXT.put("svg+xml", "svg" );
MIME_TO_EXT.put("webp", "webp");
EXT_TO_MIME.put("apng", "apng" );
EXT_TO_MIME.put("avif", "avif" );
EXT_TO_MIME.put("gif", "gif" );
EXT_TO_MIME.put("jpg", "jpeg" );
EXT_TO_MIME.put("png", "png" );
EXT_TO_MIME.put("svg", "svg+xml");
EXT_TO_MIME.put("webp", "webp" );
}
public String[] getImageType(CallableStatement stmt, int id) {
try {
stmt.setInt(1, id);
var result = stmt.executeQuery();
if (result.next()) {
var type = result.getString(1);
if (type != null) {
return new String[] { id + "." + type, EXT_TO_MIME.get(type) };
}
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
}
return null;
}
public void send(String[] image, String type, HttpServletResponse response) {
var file = (File) null;
var mimeType = (String) null;
if (image == null) {
file = new File("storage/images/default/" + type + ".png");
mimeType = "png";
} else {
file = new File("storage/images/" + type + "/" + image[0]);
mimeType = image[1];
}
try {
var stream = new FileInputStream(file);
IOUtils.copy(stream, response.getOutputStream());
response.setStatus(200);
response.setContentType("image/" + mimeType);
response.flushBuffer();
stream.close();
return;
} catch (IOException e) {
e.printStackTrace();
}
response.setStatus(500);
}
}

View file

@ -14,8 +14,21 @@ public class PlatformService {
private DbConnection dbs;
private Connection db;
@Autowired
private ImageService imageService;
@PostConstruct
private void init() {
db = dbs.getConnection();
}
public String[] getIcon(int platformId) {
try {
var stmt = db.prepareCall("{call GetPlatformIcon(?)}");
return imageService.getImageType(stmt, platformId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -0,0 +1,59 @@
package achievements.services;
import achievements.data.Achievement;
import achievements.data.query.SearchAchievements;
import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@Service
public class SearchService {
@Autowired
private DbConnection dbs;
private Connection db;
@PostConstruct
private void init() {
db = dbs.getConnection();
}
public List<Achievement> searchAchievements(SearchAchievements query) {
try {
var stmt = db.prepareCall("{call SearchAchievements(?, ?, ?, ?, ?, ?, ?, ?, ?)}");
stmt.setString(1, query.getSearchTerm());
stmt.setBoolean(3, query.isCompleted());
if (query.getUserId() != null) { stmt.setInt( 2, query.getUserId() ); } else { stmt.setString(2, null); }
if (query.getMinCompletion() != null) { stmt.setFloat(4, query.getMinCompletion()); } else { stmt.setString(4, null); }
if (query.getMaxCompletion() != null) { stmt.setFloat(5, query.getMaxCompletion()); } else { stmt.setString(5, null); }
if (query.getMinDifficulty() != null) { stmt.setFloat(6, query.getMinDifficulty()); } else { stmt.setString(6, null); }
if (query.getMaxDifficulty() != null) { stmt.setFloat(7, query.getMaxDifficulty()); } else { stmt.setString(7, null); }
if (query.getMinQuality() != null) { stmt.setFloat(8, query.getMinQuality() ); } else { stmt.setString(8, null); }
if (query.getMaxQuality() != null) { stmt.setFloat(9, query.getMaxQuality() ); } else { stmt.setString(9, null); }
var results = stmt.executeQuery();
var achievements = new ArrayList<Achievement>();
while (results.next()) {
var achievement = new Achievement();
achievement.setID (results.getInt ("ID" ));
achievement.setGame (results.getString("Game" ));
achievement.setName (results.getString("Name" ));
achievement.setCompletion(results.getInt ("Completion")); if (results.wasNull()) { achievement.setCompletion(null); }
achievement.setDifficulty(results.getFloat ("Difficulty")); if (results.wasNull()) { achievement.setDifficulty(null); }
achievement.setQuality (results.getFloat ("Quality" )); if (results.wasNull()) { achievement.setQuality (null); }
achievements.add(achievement);
}
return achievements;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -1,19 +1,24 @@
package achievements.services;
import achievements.data.Profile;
import achievements.data.query.AddPlatformRequest;
import achievements.data.query.RemovePlatformRequest;
import achievements.data.query.AddPlatform;
import achievements.data.query.RemovePlatform;
import achievements.data.query.SetUsername;
import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import static achievements.services.ImageService.MIME_TO_EXT;
@Service
public class UserService {
@ -25,6 +30,12 @@ public class UserService {
@Autowired
private AuthenticationService auth;
@Autowired
private APIService apiService;
@Autowired
private ImageService imageService;
@PostConstruct
private void init() {
db = dbs.getConnection();
@ -97,55 +108,51 @@ public class UserService {
return -1;
}
private static final HashMap<String, String> VALID_IMAGE_TYPES = new HashMap<>();
static {
VALID_IMAGE_TYPES.put("apng", "apng");
VALID_IMAGE_TYPES.put("avif", "avif");
VALID_IMAGE_TYPES.put("gif", "gif" );
VALID_IMAGE_TYPES.put("jpeg", "jpg" );
VALID_IMAGE_TYPES.put("png", "png" );
VALID_IMAGE_TYPES.put("svg+xml", "svg" );
VALID_IMAGE_TYPES.put("webp", "webp");
}
public String[] getProfileImageType(int userId) {
public String[] getProfileImage(int userId) {
try {
var stmt = db.prepareCall("{call GetUserImage(?)}");
stmt.setInt(1, userId);
var result = stmt.executeQuery();
if (result.next()) {
var type = result.getString("PFP");
if (type == null) {
return new String[] { "default", "png", "png" };
} else {
return new String[] { Integer.toString(userId), VALID_IMAGE_TYPES.get(type), type };
}
}
} catch (SQLException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
return imageService.getImageType(stmt, userId);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String setProfileImageType(int userId, String sessionKey, String type) {
public String setProfileImage(int userId, String sessionKey, MultipartFile file) {
try {
var type = file.getContentType();
if (type.matches("image/.*")) {
type = type.substring(6);
var extension = VALID_IMAGE_TYPES.get(type);
type = MIME_TO_EXT.get(type);
if (!auth.session().validate(userId, sessionKey)) {
return "forbidden";
} else if (extension == null) {
} else if (type == null) {
return "unsupported_type";
} else {
var stmt = db.prepareCall("{call SetUserImage(?, ?)}");
var stmt = db.prepareCall("{call SetUserImage(?, ?, ?)}");
stmt.setInt(1, userId);
stmt.setString(2, type);
stmt.registerOutParameter(3, Types.VARCHAR);
stmt.execute();
var oldType = stmt.getString(3);
return extension;
// Delete old file
if (oldType != null && type != oldType) {
var oldFile = new File("storage/images/user/" + userId + "." + oldType);
if (oldFile.exists()) {
oldFile.delete();
}
}
// Save new file (will overwrite old if file type didn't change)
{
var image = new FileOutputStream("storage/images/user/" + userId + "." + type);
FileCopyUtils.copy(file.getInputStream(), image);
image.close();
}
return "success";
}
} else {
return "not_an_image";
@ -156,28 +163,41 @@ public class UserService {
return "unknown";
}
public int addPlatform(int userId, AddPlatformRequest request) {
try {
if (auth.session().validate(userId, request.getSessionKey())) {
var stmt = db.prepareCall("{call AddPlatform(?, ?, ?)}");
stmt.setInt(1, userId);
stmt.setInt(2, request.getPlatformId());
stmt.setString(3, request.getPlatformUserId());
public int addPlatform(int userId, AddPlatform request) {
if (auth.session().validate(userId, request.getSessionKey())) {
try {
db.setAutoCommit(false);
try {
var stmt = db.prepareCall("{call AddUserToPlatform(?, ?, ?)}");
stmt.setInt(1, userId);
stmt.setInt(2, request.getPlatformId());
stmt.setString(3, request.getPlatformUserId());
stmt.execute();
stmt.execute();
return 0;
int successful = apiService.importUserPlatform(userId, request.getPlatformId(), request.getPlatformUserId());
if (successful == 0) {
db.commit();
db.setAutoCommit(true);
return 0;
}
} catch (Exception e) {
e.printStackTrace();
}
db.rollback();
db.setAutoCommit(true);
} catch(SQLException e){
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
public int removePlatform(int userId, RemovePlatformRequest request) {
public int removePlatform(int userId, RemovePlatform request) {
try {
if (auth.session().validate(userId, request.getSessionKey())) {
var stmt = db.prepareCall("{call RemovePlatform(?, ?)}");
var stmt = db.prepareCall("{call RemoveUserFromPlatform(?, ?)}");
stmt.setInt(1, userId);
stmt.setInt(2, request.getPlatformId());