Added SteamAPI and achievement searching
This commit is contained in:
parent
b229ff9a15
commit
627cc810ed
61 changed files with 2781 additions and 903 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
tmp/*
|
5
backend/.gitignore
vendored
5
backend/.gitignore
vendored
|
@ -23,8 +23,5 @@ src/main/resources/application-local.properties
|
|||
# Server Keystore
|
||||
src/main/resources/achievements-ssl-key.p12
|
||||
|
||||
# Api Keys
|
||||
apikeys/
|
||||
|
||||
# Program Data
|
||||
images/
|
||||
storage/
|
|
@ -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();
|
||||
}
|
||||
}
|
17
backend/src/main/java/achievements/apis/PlatformAPI.java
Normal file
17
backend/src/main/java/achievements/apis/PlatformAPI.java
Normal 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);
|
||||
}
|
102
backend/src/main/java/achievements/apis/SteamAPI.java
Normal file
102
backend/src/main/java/achievements/apis/SteamAPI.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class LoginController {
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationService authService;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("[]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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("{}");
|
||||
|
|
126
backend/src/main/java/achievements/data/APIResponse.java
Normal file
126
backend/src/main/java/achievements/data/APIResponse.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package achievements.data;
|
||||
|
||||
import achievements.data.query.StringFilter;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
41
backend/src/main/java/achievements/misc/APIList.java
Normal file
41
backend/src/main/java/achievements/misc/APIList.java
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
111
backend/src/main/java/achievements/services/APIService.java
Normal file
111
backend/src/main/java/achievements/services/APIService.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
34
backend/src/main/java/achievements/services/GameService.java
Normal file
34
backend/src/main/java/achievements/services/GameService.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -30,10 +30,16 @@ app.get("/login", (req, res) => {
|
|||
res.sendFile(path.join(__dirname + "/webpage/login.html"));
|
||||
});
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/index.html"));
|
||||
res.sendFile(path.join(__dirname + "/webpage/search_achievements.html"));
|
||||
});
|
||||
app.get("/about", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/about.html"));
|
||||
app.get("/achievements", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/search_achievements.html"));
|
||||
});
|
||||
app.get("/users", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/search_users.html"));
|
||||
});
|
||||
app.get("/games", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/search_games.html"));
|
||||
});
|
||||
app.get("/profile/:id", (req, res) => {
|
||||
res.sendFile(path.join(__dirname + "/webpage/profile.html"));
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/about.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="about-page" class="page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">About</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div class="page-subsection-wrapper">
|
||||
<p id="about-text" class="page-subsection-chunk">Collate achievement data from multiple platforms into a single location. Explore achievement data of yourself and others.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/about.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -28,6 +28,10 @@
|
|||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="importing">
|
||||
<p id="importing-text">Contemplating...</p>
|
||||
<img id="importing-loading" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
<template data-template="profile-page">
|
||||
<div id="profile-section-1">
|
||||
<div id="profile-info" class="page-subsection">
|
||||
|
@ -49,7 +53,7 @@
|
|||
</div>
|
||||
<div id="profile-info-pfp-border" class="page-subsection-chunk">
|
||||
<div id="profile-info-pfp">
|
||||
<img id="profile-info-pfp-img" src="/api/user/${id}/image" alt="User's Profile Picture" />
|
||||
<img id="profile-info-pfp-img" class="lazy-img" data-src="/api/user/${id}/image" alt="User's Profile Picture" />
|
||||
<div id="profile-info-pfp-vignette"></div>
|
||||
<img id="profile-info-pfp-upload" src="/static/res/upload.svg" alt="Upload Image" />
|
||||
<img id="profile-info-pfp-upload-hover" src="/static/res/upload-hover.svg" alt="Upload Image" />
|
||||
|
|
144
frontend/webpage/search_achievements.html
Normal file
144
frontend/webpage/search_achievements.html
Normal file
|
@ -0,0 +1,144 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_achievements.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="search-achievements-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Search Achievements</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div id="list-page-search-filters">
|
||||
<div id="list-page-search-dropdown">
|
||||
<div id="search-wrapper" class="page-subsection-wrapper">
|
||||
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
|
||||
<label id="achievement-search-button" for="achievement-search">Search</label>
|
||||
<input id="achievement-search-field" type="text" placeholder="Name" name="achievement-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
|
||||
<div id="filter-dropdown-stack">
|
||||
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
|
||||
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="list-page-filters-flex">
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Me</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="completed-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Difficulty</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Completion</p>
|
||||
<input id="min-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Completion</p>
|
||||
<input id="max-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Difficulty</p>
|
||||
<input id="min-difficulty-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Difficulty</p>
|
||||
<input id="max-difficulty-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Quality</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Quality</p>
|
||||
<input id="min-quality-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Quality</p>
|
||||
<input id="max-quality-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-list-partition page-subsection-wrapper">
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon"></p>
|
||||
<p class="list-page-entry-text achievement-game-name">Game</p>
|
||||
<p class="list-page-entry-text achievement-name">Name</p>
|
||||
<p class="list-page-entry-text achievement-completion">Completion Rate</p>
|
||||
<p class="list-page-entry-text achievement-difficulty">Difficulty</p>
|
||||
<p class="list-page-entry-text achievement-quality">Quality</p>
|
||||
</div>
|
||||
<template id="achievement-list-template" data-template="achievements-page-list: List<Basic>">
|
||||
<div class="list-page-entry">
|
||||
<img class="list-page-entry-icon lazy-img" data-src="/api/achievement/${achievement_id}/image" alt="Achievement Thumbnail"></img>
|
||||
<p class="list-page-entry-text achievement-game-name">${game_name}</p>
|
||||
<p class="list-page-entry-text achievement-name">${achievement_name}</p>
|
||||
<p class="list-page-entry-text achievement-completion">${completion}</p>
|
||||
<p class="list-page-entry-text achievement-difficulty">${difficulty}</p>
|
||||
<p class="list-page-entry-text achievement-quality">${quality}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_achievements.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/index.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_games.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
|
@ -21,10 +22,10 @@
|
|||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="index-page" class="page">
|
||||
<div id="search-games-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Achievements Project</p>
|
||||
<p class="page-header-text">Search Games</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -126,6 +127,7 @@
|
|||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/index.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_games.js"></script>
|
||||
</body>
|
||||
</html>
|
133
frontend/webpage/search_users.html
Normal file
133
frontend/webpage/search_users.html
Normal file
|
@ -0,0 +1,133 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_users.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="search-users-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Search Users</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div id="list-page-search-filters">
|
||||
<div id="list-page-search-dropdown">
|
||||
<div id="search-wrapper" class="page-subsection-wrapper">
|
||||
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
|
||||
<label for="achievement-search">Search</label>
|
||||
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
|
||||
<div id="filter-dropdown-stack">
|
||||
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
|
||||
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="list-page-filters-flex">
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Games</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="games-owned-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Games Owned</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">General</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="from-games-owned-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">From My Games</p>
|
||||
</div>
|
||||
<div id="completed-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Completed</p>
|
||||
</div>
|
||||
<div id="completed-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Platforms</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="games-owned-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Games Owned</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-list-partition page-subsection-wrapper">
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon"></p>
|
||||
<p class="list-page-entry-text achievement-name">Name</p>
|
||||
<p class="list-page-entry-text achievement-description">Description</p>
|
||||
<p class="list-page-entry-text achievement-stages">Stages</p>
|
||||
</div>
|
||||
<template data-template="achievements-page-list: List<Basic>">
|
||||
<div class="list-page-entry">
|
||||
<img class="list-page-entry-icon" src="/static/res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
|
||||
<div class="list-page-entry-text-section">
|
||||
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
|
||||
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
|
||||
<p class="list-page-entry-text achievement-stages">${stages}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_users.js"></script>
|
||||
</body>
|
||||
</html>
|
1
frontend/webpage/static/res/loading.svg
Normal file
1
frontend/webpage/static/res/loading.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M500,904C276.877,904,96,723.12305,96,500S276.877,96,500,96V0C223.8576,0,0,223.8576,0,500c0,276.14233,223.8576,500,500,500A498.43514,498.43514,0,0,0,853.55334,853.55334l-67.8822-67.8822A402.7356,402.7356,0,0,1,500,904Z"/></svg>
|
After Width: | Height: | Size: 392 B |
|
@ -25,7 +25,6 @@ const loadSession = async () => {
|
|||
|
||||
await fetch(`/api/auth/refresh`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -52,8 +51,10 @@ const commonTemplates = async () => {
|
|||
{ section: "right" }
|
||||
]);
|
||||
template.apply("navbar-section-left").values([
|
||||
{ item: "project", title: "Project" },
|
||||
{ item: "about", title: "About" }
|
||||
{ item: "achievements", title: "Achievements" },
|
||||
{ item: "users", title: "Users" },
|
||||
{ item: "games", title: "Games" },
|
||||
{ item: "import", title: "Import" }
|
||||
]);
|
||||
if (session) {
|
||||
template.apply("navbar-section-right").values([
|
||||
|
@ -62,34 +63,40 @@ const commonTemplates = async () => {
|
|||
]);
|
||||
} else {
|
||||
template.apply("navbar-section-right").values([
|
||||
{ item: "login", title: "Login" }
|
||||
{ item: "login", title: "Login" }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadLazyImages = () => {
|
||||
const imgs = document.querySelectorAll(".lazy-img");
|
||||
for (const img of imgs) {
|
||||
img.src = img.dataset.src;
|
||||
}
|
||||
}
|
||||
|
||||
const connectNavbar = () => {
|
||||
const navItems = document.querySelectorAll(".navbar-item");
|
||||
|
||||
if (!session || !session.admin) {
|
||||
document.querySelector("#navbar-item-import").remove();
|
||||
}
|
||||
|
||||
for (const item of navItems) {
|
||||
if (item.dataset.pageName === "logout") {
|
||||
item.addEventListener("click", (clickEvent) => {
|
||||
fetch(`/api/auth/logout`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ key: session.key })
|
||||
})
|
||||
.then(response => {
|
||||
session = undefined;
|
||||
window.location.href = "/login";
|
||||
});
|
||||
session = undefined;
|
||||
window.location.href = "/login";
|
||||
});
|
||||
} else if (item.dataset.pageName === "profile") {
|
||||
item.addEventListener("click", (clickEvent) => window.location.href = `/profile/${session.id}`);
|
||||
} else if (item.dataset.pageName === "project") {
|
||||
item.addEventListener("click", (clickEvent) => window.location.href = `/`);
|
||||
} else {
|
||||
item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`);
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
const expandTemplates = async () => {
|
||||
await commonTemplates();
|
||||
}
|
||||
|
||||
const loadFilters = () => {
|
||||
const filtersButton = document.querySelector("#filter-dropdown-stack");
|
||||
const filters = document.querySelector("#list-page-filters-flex");
|
||||
|
||||
filtersButton.addEventListener("click", (clickEvent) => {
|
||||
filtersButton.classList.toggle("active");
|
||||
filters.classList.toggle("active");
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", async (loadEvent) => {
|
||||
loadRoot();
|
||||
loadSession();
|
||||
|
||||
await expandTemplates();
|
||||
await template.expand();
|
||||
|
||||
connectNavbar();
|
||||
loadFilters();
|
||||
});
|
|
@ -11,6 +11,7 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
password: document.querySelector("#password"),
|
||||
confirm: document.querySelector("#confirm" )
|
||||
};
|
||||
fields.email.focus();
|
||||
|
||||
const createUser = document.querySelector("#create-user-button");
|
||||
const login = document.querySelector("#login-button");
|
||||
|
@ -80,7 +81,6 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
freeze();
|
||||
fetch(`/api/auth/create_user`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -141,7 +141,6 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
freeze();
|
||||
fetch(`/api/auth/login`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
|
|
@ -2,14 +2,25 @@ let profileId = window.location.pathname.split('/').pop();
|
|||
let isReturn = false;
|
||||
let profileData = null;
|
||||
const loadProfile = () => {
|
||||
{
|
||||
const lists = document.querySelectorAll(".profile-list");
|
||||
const lists = document.querySelectorAll(".profile-list");
|
||||
const checkLists = () => {
|
||||
for (const list of lists) {
|
||||
if (list.querySelectorAll(".profile-entry").length === 0) {
|
||||
list.parentElement.removeChild(list);
|
||||
let found = false;
|
||||
const entries = list.querySelectorAll(".profile-entry");
|
||||
for (const entry of entries) {
|
||||
if (window.getComputedStyle(entry).getPropertyValue('display') !== 'none') {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
list.style.display = 'none';
|
||||
} else {
|
||||
list.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
checkLists();
|
||||
|
||||
{
|
||||
const validImageFile = (type) => {
|
||||
|
@ -32,7 +43,6 @@ const loadProfile = () => {
|
|||
if (usernameField.value !== '') {
|
||||
fetch(`/api/user/${profileId}/username`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -89,7 +99,6 @@ const loadProfile = () => {
|
|||
|
||||
fetch(`/api/user/${profileId}/image`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: data
|
||||
}).then(response => {
|
||||
if (upload.classList.contains("active")) {
|
||||
|
@ -141,6 +150,7 @@ const loadProfile = () => {
|
|||
for (const platform of platforms) {
|
||||
platform.classList.toggle("editing");
|
||||
}
|
||||
checkLists();
|
||||
};
|
||||
editPlatformsButton.addEventListener("click", togglePlatformEdit);
|
||||
savePlatformsButton.addEventListener("click", togglePlatformEdit);
|
||||
|
@ -156,7 +166,6 @@ const loadProfile = () => {
|
|||
steamButtons[1].addEventListener("click", (clickEvent) => {
|
||||
fetch(`/api/user/${profileId}/platforms/remove`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -213,11 +222,11 @@ const expandTemplates = async () => {
|
|||
template.apply("profile-platforms-list").promise(profileData.then(data =>
|
||||
data.platforms.map(platform => ({
|
||||
platform_id: platform.id,
|
||||
img: `<img class="profile-entry-icon" src="/api/platform/image/${platform.id}" alt="Steam Logo" />`,
|
||||
img: `<img class="profile-entry-icon" src="/api/platform/${platform.id}/image" alt="Steam Logo" />`,
|
||||
name: platform.name,
|
||||
connected: platform.connected ? "connected" : "",
|
||||
add:
|
||||
(platform.id === 0 ? `<img id="add-steam" class="platform-add" src="https://community.cloudflare.steamstatic.com/public/images/signinthroughsteam/sits_01.png" alt="Add" />` :
|
||||
(platform.id === 0 ? `<img id="add-steam" class="platform-add" src="https://steamcdn-a.akamaihd.net/steamcommunity/public/images/steamworks_docs/english/sits_small.png" alt="Add" />` :
|
||||
(platform.id === 1 ? `<p class="platform-unsupported">Coming soon...</p>` :
|
||||
(platform.id === 2 ? `<p class="platform-unsupported">Coming soon...</p>` :
|
||||
"")))
|
||||
|
@ -228,6 +237,7 @@ const expandTemplates = async () => {
|
|||
window.addEventListener("load", async (loadEvent) => {
|
||||
await loadCommon();
|
||||
|
||||
var importing = document.querySelector("#importing");
|
||||
if (!/\d+/.test(profileId)) {
|
||||
isReturn = true;
|
||||
const platform = profileId;
|
||||
|
@ -238,6 +248,9 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
delete session.lastProfile;
|
||||
}
|
||||
|
||||
const importingText = importing.querySelector("#importing-text");
|
||||
importingText.textContent = `Importing from ${platform}...`;
|
||||
importing.style.display = `flex`;
|
||||
if (platform === 'steam') {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
|
||||
|
@ -246,9 +259,8 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
} else {
|
||||
// Regex courtesy of https://github.com/liamcurry/passport-steam/blob/master/lib/passport-steam/strategy.js
|
||||
var steamId = /^https?:\/\/steamcommunity\.com\/openid\/id\/(\d+)$/.exec(query.get('openid.claimed_id'))[1];
|
||||
await fetch("/api/user/platforms/add", {
|
||||
await fetch(`/api/user/${profileId}/platforms/add`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -266,13 +278,15 @@ window.addEventListener("load", async (loadEvent) => {
|
|||
} else {
|
||||
// Handle error
|
||||
}
|
||||
importing.remove();
|
||||
|
||||
profileData = fetch(`/api/user/${profileId}`, { method: 'GET', mode: 'cors' })
|
||||
profileData = fetch(`/api/user/${profileId}`, { method: 'GET' })
|
||||
.then(response => response.json());
|
||||
|
||||
await expandTemplates();
|
||||
await template.expand();
|
||||
|
||||
loadLazyImages();
|
||||
connectNavbar();
|
||||
loadProfile();
|
||||
});
|
26
frontend/webpage/static/scripts/search.js
Normal file
26
frontend/webpage/static/scripts/search.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const expandTemplates = async () => {
|
||||
await commonTemplates();
|
||||
}
|
||||
|
||||
const loadFilters = () => {
|
||||
const filtersButton = document.querySelector("#filter-dropdown-stack");
|
||||
const filtersSection = document.querySelector("#list-page-filters-flex");
|
||||
|
||||
filtersButton.addEventListener("click", (clickEvent) => {
|
||||
filtersButton.classList.toggle("active");
|
||||
filtersSection.classList.toggle("active");
|
||||
});
|
||||
|
||||
const filterCheckboxes = document.querySelectorAll(".list-page-filter-checkbox");
|
||||
for (const checkbox of filterCheckboxes) {
|
||||
checkbox.parentElement.addEventListener("click", (clickEvent) => {
|
||||
checkbox.parentElement.classList.toggle("selected");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadCommonSearch = async () => {
|
||||
await loadCommon();
|
||||
|
||||
await expandTemplates();
|
||||
};
|
108
frontend/webpage/static/scripts/search_achievements.js
Normal file
108
frontend/webpage/static/scripts/search_achievements.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
let templateList = null;
|
||||
let templateText = null;
|
||||
const saveTemplate = () => {
|
||||
const templateElement = document.querySelector("#achievement-list-template");
|
||||
templateList = templateElement.parentElement;
|
||||
templateText = templateElement.outerHTML;
|
||||
templateElement.remove();
|
||||
};
|
||||
|
||||
const loadAchievementSearch = () => {
|
||||
const loading = document.querySelector("#loading-results");
|
||||
|
||||
|
||||
const searchButton = document.querySelector("#achievement-search-button");
|
||||
const searchField = document.querySelector("#achievement-search-field" );
|
||||
|
||||
const completed = document.querySelector("#completed-filter");
|
||||
const minCompletion = document.querySelector("#min-completion-filter");
|
||||
const maxCompletion = document.querySelector("#max-completion-filter");
|
||||
const minDifficulty = document.querySelector("#min-difficulty-filter");
|
||||
const maxDifficulty = document.querySelector("#max-difficulty-filter");
|
||||
const minQuality = document.querySelector("#min-quality-filter" );
|
||||
const maxQuality = document.querySelector("#max-quality-filter" );
|
||||
|
||||
let canSearch = true;
|
||||
const loadList = async () => {
|
||||
if (canSearch) {
|
||||
canSearch = false;
|
||||
|
||||
const body = {
|
||||
searchTerm: searchField.value,
|
||||
userId: completed.classList.contains('active') ? session.id : null,
|
||||
completed: completed.classList.contains('active'),
|
||||
minCompletion: minCompletion.value === '' ? null : Number(minCompletion.value),
|
||||
maxCompletion: maxCompletion.value === '' ? null : Number(maxCompletion.value),
|
||||
minDifficulty: minDifficulty.value === '' ? null : Number(minDifficulty.value),
|
||||
maxDifficulty: maxDifficulty.value === '' ? null : Number(maxDifficulty.value),
|
||||
minQuality: minQuality.value === '' ? null : Number(minQuality.value ),
|
||||
maxQuality: maxQuality.value === '' ? null : Number(maxQuality.value ),
|
||||
};
|
||||
console.log(body);
|
||||
let successful = true;
|
||||
if (Number.isNaN(body.minCompletion)) { successful = false; minCompletion.style.backgroundColor = 'var(--error)'; } else { minCompletion.style.backgroundColor = 'var(--foreground)'; }
|
||||
if (Number.isNaN(body.maxCompletion)) { successful = false; maxCompletion.style.backgroundColor = 'var(--error)'; } else { maxCompletion.style.backgroundColor = 'var(--foreground)'; }
|
||||
if (Number.isNaN(body.minDifficulty)) { successful = false; minDifficulty.style.backgroundColor = 'var(--error)'; } else { minDifficulty.style.backgroundColor = 'var(--foreground)'; }
|
||||
if (Number.isNaN(body.maxDifficulty)) { successful = false; maxDifficulty.style.backgroundColor = 'var(--error)'; } else { maxDifficulty.style.backgroundColor = 'var(--foreground)'; }
|
||||
if (Number.isNaN(body.minQuality )) { successful = false; minQuality.style.backgroundColor = 'var(--error)'; } else { minQuality.style.backgroundColor = 'var(--foreground)'; }
|
||||
if (Number.isNaN(body.maxQuality )) { successful = false; maxQuality.style.backgroundColor = 'var(--error)'; } else { maxQuality.style.backgroundColor = 'var(--foreground)'; }
|
||||
|
||||
if (!successful) {
|
||||
canSearch = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of templateList.querySelectorAll(".list-page-entry")) {
|
||||
entry.remove();
|
||||
}
|
||||
templateList.innerHTML += templateText;
|
||||
loading.style.display = 'block';
|
||||
|
||||
const data = fetch("/api/achievements", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => response.json())
|
||||
|
||||
template.clear();
|
||||
template.apply('achievements-page-list').promise(data.then(data => data.map(item => ({
|
||||
achievement_id: item.ID,
|
||||
achievement_name: item.name,
|
||||
game_name: item.game,
|
||||
completion: item.completion == null ? 'N/A' : item.completion + '%',
|
||||
difficulty: item.difficulty == null ? 'N/A' : item.difficulty + ' / 10',
|
||||
quality: item.quality == null ? 'N/A' : item.quality + ' / 10'
|
||||
}))));
|
||||
await template.expand();
|
||||
data.then(data => {
|
||||
loading.style.display = 'none';
|
||||
canSearch = true;
|
||||
loadLazyImages();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
searchButton.addEventListener("click", loadList);
|
||||
searchField.addEventListener("keydown", (keyEvent) => {
|
||||
if (keyEvent.key === 'Enter') {
|
||||
loadList();
|
||||
}
|
||||
});
|
||||
|
||||
loadList();
|
||||
};
|
||||
|
||||
window.addEventListener("load", async (loadEvent) => {
|
||||
await loadCommonSearch();
|
||||
|
||||
saveTemplate();
|
||||
await template.expand();
|
||||
|
||||
connectNavbar();
|
||||
loadFilters();
|
||||
|
||||
await loadAchievementSearch();
|
||||
});
|
|
@ -136,6 +136,10 @@ var template = template || {};
|
|||
}
|
||||
};
|
||||
|
||||
template.clear = () => {
|
||||
templateEntryMap.clear();
|
||||
}
|
||||
|
||||
const parseType = (type) => {
|
||||
let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/);
|
||||
let id = result[1];
|
||||
|
|
|
@ -70,6 +70,15 @@ html, body {
|
|||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
from { transform: rotateZ(0deg ); }
|
||||
to { transform: rotateZ(360deg); }
|
||||
}
|
||||
|
||||
.ap-loading {
|
||||
animation: 1.5s cubic-bezier(0.4, 0.15, 0.6, 0.85) 0s infinite running load;
|
||||
}
|
||||
|
||||
.ap-button {
|
||||
color: var(--foreground);
|
||||
background-color: var(--accent-value2);
|
||||
|
@ -226,201 +235,3 @@ html, body {
|
|||
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search {
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-search > label,
|
||||
.list-page-search > input {
|
||||
box-sizing: border-box;
|
||||
padding: 12px 20px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-search > label {
|
||||
background-color: var(--accent-value2);
|
||||
}
|
||||
|
||||
.list-page-search > label:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search > label:active {
|
||||
background-color: var(--accent-value1);
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-search > input {
|
||||
background-color: var(--distinction);
|
||||
|
||||
border: 0;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
outline: none;
|
||||
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.075s;
|
||||
}
|
||||
|
||||
.list-page-search > input:focus {
|
||||
background-color: var(--foreground);
|
||||
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
.list-page-partitions {
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-page-filter-partition {
|
||||
width: 20%;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.list-page-filter-chunk {
|
||||
background-color: var(--distinction);
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-page-filter {
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-filter-checkbox {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
background-color: var(--foreground);
|
||||
|
||||
border: 3px solid var(--foreground);
|
||||
border-radius: 8px;
|
||||
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-filter:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--foreground);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent1);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent0);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter-name {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.list-page-list-partition {
|
||||
box-sizing: border-box;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-page-list {
|
||||
border-radius: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-page-header {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-page-entry {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-entry-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.list-page-entry-text {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
border-top: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.list-page-header > .list-page-entry-text {
|
||||
border: 0;
|
||||
}
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
#index-page {
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
#list-page-search-filters {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#list-page-search-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#search-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#list-page-search-pair {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#filter-dropdown-wrapper {
|
||||
box-sizing: border-box;
|
||||
height: 84px;
|
||||
width: 84px;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack.active {
|
||||
transform: rotateZ(-90deg);
|
||||
}
|
||||
|
||||
#filter-dropdown-button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack:hover > #filter-dropdown-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#filter-dropdown-button-hover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack:hover > #filter-dropdown-button-hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#list-page-filters-flex {
|
||||
display: none;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#list-page-filters-flex.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list-page-filter-section {
|
||||
box-sizing: border-box;
|
||||
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#list-page-filters-background {
|
||||
background-color: var(--distinction);
|
||||
}
|
||||
|
||||
.list-page-entry-text.achievement-name {
|
||||
flex-grow: 3;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.list-page-entry-text.achievement-description {
|
||||
flex-grow: 6;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.list-page-entry-text.achievement-stages {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
--form-spacing: 48px;
|
||||
|
||||
--element-spacing: 12px;
|
||||
|
||||
--error: #F95959;
|
||||
}
|
||||
|
||||
#login-page {
|
||||
|
|
|
@ -2,6 +2,26 @@
|
|||
max-width: 1600px;
|
||||
}
|
||||
|
||||
#importing {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
#importing-text {
|
||||
margin: 0;
|
||||
height: 96px;
|
||||
font-size: 64px;
|
||||
line-height: 96px;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
#importing-loading {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.profile-list {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
@ -150,7 +170,7 @@
|
|||
border-radius: 8px;
|
||||
object-fit: contain;
|
||||
|
||||
background-color: var(--background);
|
||||
background-color: var(--background-dark);
|
||||
|
||||
position: absolute;
|
||||
}
|
||||
|
@ -178,7 +198,7 @@
|
|||
|
||||
border-radius: 8px;
|
||||
|
||||
background-color: var(--background);
|
||||
background-color: var(--background-dark);
|
||||
opacity: 0.8;
|
||||
|
||||
display: block;
|
||||
|
|
330
frontend/webpage/static/styles/search.css
Normal file
330
frontend/webpage/static/styles/search.css
Normal file
|
@ -0,0 +1,330 @@
|
|||
#list-page-search-filters {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#list-page-search-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#search-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#list-page-search-pair {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#filter-dropdown-wrapper {
|
||||
box-sizing: border-box;
|
||||
height: 84px;
|
||||
width: 84px;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack.active {
|
||||
transform: rotateZ(-90deg);
|
||||
}
|
||||
|
||||
#filter-dropdown-button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack:hover > #filter-dropdown-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#filter-dropdown-button-hover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
#filter-dropdown-stack:hover > #filter-dropdown-button-hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#list-page-filters-flex {
|
||||
display: none;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#list-page-filters-flex.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list-page-filter-section {
|
||||
box-sizing: border-box;
|
||||
|
||||
flex-basis: max-content;
|
||||
flex-grow: 1;
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-page-filter-partition {
|
||||
width: 20%;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.list-page-filter-chunk {
|
||||
background-color: var(--distinction);
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-page-filter {
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-filter-checkbox {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
background-color: var(--foreground);
|
||||
|
||||
border: 3px solid var(--foreground);
|
||||
border-radius: 8px;
|
||||
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-filter:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--foreground);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent1);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent0);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter-name,
|
||||
.list-page-filter-label {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.list-page-filter-label {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.list-page-filter-param {
|
||||
padding: 4px;
|
||||
width: 25%;
|
||||
|
||||
font-size: 24px;
|
||||
color: var(--background);
|
||||
background-color: var(--foreground);
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#list-page-filters-background {
|
||||
background-color: var(--distinction);
|
||||
}
|
||||
|
||||
.list-page-entry-text {
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.page.search {
|
||||
max-width: 1720px;
|
||||
}
|
||||
|
||||
.list-page-search {
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-search > label,
|
||||
.list-page-search > input {
|
||||
box-sizing: border-box;
|
||||
padding: 12px 20px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-search > label {
|
||||
background-color: var(--accent-value2);
|
||||
}
|
||||
|
||||
.list-page-search > label:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search > label:active {
|
||||
background-color: var(--accent-value1);
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-search > input {
|
||||
background-color: var(--distinction);
|
||||
|
||||
border: 0;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
outline: none;
|
||||
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.075s;
|
||||
}
|
||||
|
||||
.list-page-search > input:focus {
|
||||
background-color: var(--foreground);
|
||||
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
.list-page-partitions {
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.list-page-list-partition {
|
||||
box-sizing: border-box;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-page-list {
|
||||
border-radius: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-page-header {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-page-entry {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-entry-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.list-page-entry-text {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-page-entry > .list-page-entry-text {
|
||||
border-top: 1px solid var(--background);
|
||||
border-left: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.list-page-header > .list-page-entry-text {
|
||||
border-left: 1px solid var(--accent-value0);
|
||||
}
|
||||
|
||||
#loading-results {
|
||||
margin: 16px 0;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
object-fit: contain;
|
||||
|
||||
display: none;
|
||||
}
|
5
frontend/webpage/static/styles/search_achievements.css
Normal file
5
frontend/webpage/static/styles/search_achievements.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.list-page-entry-text.achievement-game-name { flex-grow: 1.75; }
|
||||
.list-page-entry-text.achievement-name { flex-grow: 2; }
|
||||
.list-page-entry-text.achievement-completion { flex-grow: 1; }
|
||||
.list-page-entry-text.achievement-quality { flex-grow: 1; }
|
||||
.list-page-entry-text.achievement-difficulty { flex-grow: 1; }
|
|
@ -18,4 +18,6 @@
|
|||
|
||||
--selected-accent0: #2266CC;
|
||||
--selected-accent1: #3388FF;
|
||||
|
||||
--error: #F95959;
|
||||
}
|
||||
|
|
Binary file not shown.
639
sql/DataProcs.sql
Normal file
639
sql/DataProcs.sql
Normal file
|
@ -0,0 +1,639 @@
|
|||
---------------------------------------
|
||||
-- GET USER NAME AND STATS PROCEDURE --
|
||||
---------------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserNameAndStats(
|
||||
@userId INT,
|
||||
@username VARCHAR(32) OUTPUT,
|
||||
@completed INT OUTPUT,
|
||||
@average INT OUTPUT,
|
||||
@perfect INT OUTPUT
|
||||
)
|
||||
AS
|
||||
|
||||
SELECT @username = Username
|
||||
FROM [User]
|
||||
WHERE ID = @userId
|
||||
|
||||
IF @username IS NULL
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
|
||||
SELECT @completed = SUM(Completed)
|
||||
FROM GameCompletionByUser
|
||||
WHERE UserID = @userId
|
||||
|
||||
SELECT @average = AVG((Completed * 100) / Total)
|
||||
FROM GameCompletionByUser
|
||||
WHERE UserID = @userId
|
||||
|
||||
SELECT @perfect = COUNT(GameID)
|
||||
FROM GameCompletionByUser
|
||||
WHERE UserID = @userId AND Completed = Total
|
||||
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
SELECT * FROM [User]
|
||||
|
||||
----------------------------------
|
||||
-- GET USER PLATFORMS PROCEDURE --
|
||||
----------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserPlatforms(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT [Platform].ID, [PlatformName], (CASE WHEN UserID IS NOT NULL THEN 1 ELSE 0 END) AS Connected
|
||||
FROM [Platform]
|
||||
LEFT JOIN IsOn ON IsOn.PlatformID = [Platform].ID AND UserID = @userId
|
||||
ORDER BY [Platform].ID
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------------------------
|
||||
-- GET USER RATINGS PROCEDURE --
|
||||
--------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserRatings(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT Game.Name AS GameName, Achievement.Name AS AchievementName, Quality, Difficulty, Rating.[Description]
|
||||
FROM Rating
|
||||
JOIN Achievement ON Achievement.ID = Rating.AchievementID
|
||||
JOIN Game ON Game.ID = Achievement.GameID
|
||||
WHERE UserID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------------------
|
||||
-- GET USER IMAGE PROCEDURE --
|
||||
------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserImage(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT ProfileImage FROM [User] WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------
|
||||
-- SET USERNAME --
|
||||
------------------
|
||||
|
||||
CREATE PROCEDURE SetUsername(
|
||||
@userId INT,
|
||||
@username VARCHAR(32)
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
UPDATE [User] SET Username = @username WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------------------
|
||||
-- SET USER IMAGE PROCEDURE --
|
||||
------------------------------
|
||||
|
||||
CREATE PROCEDURE SetUserImage(
|
||||
@userId INT,
|
||||
@type ImageType,
|
||||
@oldType ImageType OUTPUT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT @oldType = ProfileImage FROM [User] WHERE ID = @userId
|
||||
UPDATE [User] SET ProfileImage = @type WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------------------
|
||||
-- ADD USER TO PLATFORM --
|
||||
--------------------------
|
||||
|
||||
CREATE PROCEDURE AddUserToPlatform(
|
||||
@userId INT,
|
||||
@platformId INT,
|
||||
@platformUserID VARCHAR(32)
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF EXISTS (SELECT * FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'User already exists on specified platform'
|
||||
RETURN 3
|
||||
END
|
||||
INSERT INTO IsOn VALUES (@userId, @platformId, @platformUserId)
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-------------------------------
|
||||
-- REMOVE USER FROM PLATFORM --
|
||||
-------------------------------
|
||||
|
||||
CREATE PROCEDURE RemoveUserFromPlatform(
|
||||
@userId INT,
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT EXISTS (SELECT UserID FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'User does not exist on specified platform'
|
||||
RETURN 3
|
||||
END
|
||||
DELETE FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId
|
||||
DELETE FROM Progress WHERE UserID = @userId AND PlatformID = @platformId
|
||||
DELETE FROM Owns WHERE UserID = @userId AND PlatformID = @platformId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------
|
||||
-- ADD PLATFORM --
|
||||
------------------
|
||||
|
||||
CREATE PROCEDURE AddPlatform(
|
||||
@name VARCHAR(32),
|
||||
@platformId INT OUTPUT
|
||||
)
|
||||
AS
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'Platform name cannot be null'
|
||||
RETURN 1
|
||||
END
|
||||
INSERT INTO [Platform] VALUES (@name)
|
||||
SET @platformId = @@IDENTITY
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------
|
||||
-- REMOVE PLATFORM --
|
||||
---------------------
|
||||
|
||||
CREATE PROCEDURE RemovePlatform(
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF @platformId IN (SELECT PlatformID FROM ExistsOn)
|
||||
BEGIN
|
||||
PRINT 'All games must be removed from the specified platform before it can be removed'
|
||||
RETURN 2
|
||||
END
|
||||
DELETE FROM [Platform] WHERE ID = @platformId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-------------------
|
||||
-- GET PLATFORMS --
|
||||
-------------------
|
||||
|
||||
CREATE PROCEDURE GetPlatforms
|
||||
AS
|
||||
SELECT ID, PlatformName FROM [Platform]
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-----------------------
|
||||
-- GET PLATFORM NAME --
|
||||
-----------------------
|
||||
|
||||
CREATE PROCEDURE GetPlatformName(
|
||||
@platformId INT,
|
||||
@name VARCHAR(32) OUTPUT
|
||||
)
|
||||
AS
|
||||
SELECT @name = PlatformName FROM [Platform] WHERE ID = @platformId
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-----------------------
|
||||
-- GET PLATFORM ICON --
|
||||
-----------------------
|
||||
|
||||
CREATE PROCEDURE GetPlatformIcon(
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT Icon FROM [Platform] WHERE ID = @platformId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------
|
||||
-- ADD GAME --
|
||||
--------------
|
||||
|
||||
CREATE PROCEDURE AddGame(
|
||||
@name VARCHAR(32),
|
||||
@image ImageType,
|
||||
@gameId INT OUTPUT
|
||||
)
|
||||
AS
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'Game name cannot be null'
|
||||
RETURN 1
|
||||
END
|
||||
IF @name IN (SELECT [Name] FROM Game)
|
||||
BEGIN
|
||||
PRINT 'Game with specified name already exists'
|
||||
RETURN 2
|
||||
END
|
||||
INSERT INTO Game VALUES (@name, @image)
|
||||
SET @gameId = @@IDENTITY
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------
|
||||
-- ADD IF NOT GAME --
|
||||
---------------------
|
||||
|
||||
CREATE PROCEDURE AddIfNotGame(
|
||||
@name VARCHAR(32),
|
||||
@image VARCHAR(11),
|
||||
@gameId INT OUTPUT
|
||||
)
|
||||
AS
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'Game name cannot be null'
|
||||
RETURN 1
|
||||
END
|
||||
-- Ideally game name wouldn't have to be unique, but I don't know of another way to sync games across platforms when they share no IDing system
|
||||
IF NOT @name IN (SELECT [Name] FROM Game)
|
||||
BEGIN
|
||||
INSERT INTO Game VALUES (@name, @image)
|
||||
END
|
||||
SELECT @gameId = ID FROM Game WHERE [Name] = @name
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-----------------
|
||||
-- REMOVE GAME --
|
||||
-----------------
|
||||
|
||||
CREATE PROCEDURE RemoveGame(
|
||||
@gameId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
DELETE FROM Game WHERE ID = @gameId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-------------------
|
||||
-- GET GAME ICON --
|
||||
-------------------
|
||||
|
||||
CREATE PROCEDURE GetGameIcon(
|
||||
@gameId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM [Game])
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT Icon FROM [Game] WHERE ID = @gameId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
----------------------
|
||||
-- ADD GAME TO USER --
|
||||
----------------------
|
||||
|
||||
CREATE PROCEDURE AddGameToUser(
|
||||
@gameId INT,
|
||||
@userId INT,
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 3
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'User is not on specified platform'
|
||||
RETURN 4
|
||||
END
|
||||
IF EXISTS (SELECT * FROM Owns WHERE GameID = @gameId AND UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'Game is already owned by specified user on specified platform'
|
||||
RETURN 5
|
||||
END
|
||||
INSERT INTO Owns VALUES (@userId, @gameId, @platformId)
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------------
|
||||
-- REMOVE GAME FROM USER --
|
||||
---------------------------
|
||||
|
||||
CREATE PROCEDURE RemoveGameFromUser(
|
||||
@gameId INT,
|
||||
@userId INT,
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 3
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM Owns WHERE GameID = @gameId AND UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'Game is not owned by specified user on specified platform'
|
||||
RETURN 4
|
||||
END
|
||||
DELETE FROM Owns WHERE UserID = @userId AND GameID = @gameId AND PlatformID = @platformId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------------------
|
||||
-- ADD GAME TO PLATFORM --
|
||||
--------------------------
|
||||
|
||||
CREATE PROCEDURE AddGameToPlatform(
|
||||
@gameId INT,
|
||||
@platformId INT,
|
||||
@platformGameId VARCHAR(32)
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF EXISTS (SELECT * FROM ExistsOn WHERE GameID = @gameId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'Game already exists on specified platform'
|
||||
RETURN 3
|
||||
END
|
||||
INSERT INTO ExistsOn VALUES (@gameId, @platformId, @platformGameId)
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
-------------------------------
|
||||
-- REMOVE GAME FROM PLATFORM --
|
||||
-------------------------------
|
||||
|
||||
CREATE PROCEDURE RemoveGameFromPlatform(
|
||||
@gameId INT,
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM ExistsOn WHERE GameID = @gameId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'Game does not exist on specified platform'
|
||||
RETURN 3
|
||||
END
|
||||
DELETE FROM ExistsOn WHERE GameID = @gameId AND PlatformID = @platformId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------
|
||||
-- ADD ACHIEVEMENT --
|
||||
---------------------
|
||||
|
||||
CREATE PROCEDURE AddAchievement(
|
||||
@gameId INT,
|
||||
@name VARCHAR(128),
|
||||
@description VARCHAR(512),
|
||||
@stages INT,
|
||||
@image ImageType,
|
||||
@achievementId INT OUTPUT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'Achievement name cannot be null'
|
||||
RETURN 2
|
||||
END
|
||||
IF @stages IS NULL
|
||||
BEGIN
|
||||
PRINT 'Achievement stages cannot be null'
|
||||
RETURN 3
|
||||
END
|
||||
IF @name IN (SELECT [Name] FROM Achievement WHERE GameID = @gameId)
|
||||
BEGIN
|
||||
PRINT 'Achievement with specified name already exists for specified game'
|
||||
RETURN 4
|
||||
END
|
||||
INSERT INTO Achievement VALUES (@gameId, @name, @description, @stages, @image)
|
||||
SET @achievementId = @@IDENTITY
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
----------------------------
|
||||
-- ADD IF NOT ACHIEVEMENT --
|
||||
----------------------------
|
||||
|
||||
CREATE PROCEDURE AddIfNotAchievement(
|
||||
@gameId INT,
|
||||
@name VARCHAR(128),
|
||||
@description VARCHAR(512),
|
||||
@stages INT,
|
||||
@image VARCHAR(11),
|
||||
@achievementId INT OUTPUT
|
||||
)
|
||||
AS
|
||||
IF NOT @gameId IN (SELECT ID FROM Game)
|
||||
BEGIN
|
||||
PRINT 'No game with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF @name IS NULL
|
||||
BEGIN
|
||||
PRINT 'Achievement name cannot be null'
|
||||
RETURN 2
|
||||
END
|
||||
IF @stages IS NULL
|
||||
BEGIN
|
||||
PRINT 'Achievement stages cannot be null'
|
||||
RETURN 3
|
||||
END
|
||||
IF NOT @name IN (SELECT [Name] FROM Achievement WHERE GameID = @gameId)
|
||||
BEGIN
|
||||
INSERT INTO Achievement VALUES (@gameId, @name, @description, @stages, @image)
|
||||
END
|
||||
SELECT @achievementId = ID FROM Achievement WHERE [Name] = @name AND GameID = @gameId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------------
|
||||
-- REMOVE ACHIEVEMENT --
|
||||
------------------------
|
||||
|
||||
CREATE PROCEDURE RemoveAchievement(
|
||||
@achievementId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @achievementId IN (SELECT ID FROM Achievement)
|
||||
BEGIN
|
||||
PRINT 'No achievement with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
DELETE FROM Achievement WHERE ID = @achievementId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------------------
|
||||
-- GET ACHIEVEMENT ICON --
|
||||
--------------------------
|
||||
|
||||
CREATE PROCEDURE GetAchievementIcon(
|
||||
@achievementId INT
|
||||
)
|
||||
AS
|
||||
IF NOT @achievementId IN (SELECT ID FROM Achievement)
|
||||
BEGIN
|
||||
PRINT 'No achievement with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT Icon FROM Achievement WHERE ID = @achievementId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------------------------
|
||||
-- SET ACHIEVEMENT PROGRESS FOR USER --
|
||||
---------------------------------------
|
||||
|
||||
CREATE PROCEDURE SetAchievementProgressForUser(
|
||||
@userId INT,
|
||||
@platformId INT,
|
||||
@achievementId INT,
|
||||
@progress INT
|
||||
)
|
||||
AS
|
||||
IF NOT @userId IN (SELECT ID FROM [User])
|
||||
BEGIN
|
||||
PRINT 'No user with the specified ID was found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT @platformId IN (SELECT ID FROM [Platform])
|
||||
BEGIN
|
||||
PRINT 'No platform with the specified ID was found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT @achievementId IN (SELECT ID FROM Achievement)
|
||||
BEGIN
|
||||
PRINT 'No achievement with the specified ID was found'
|
||||
RETURN 3
|
||||
END
|
||||
IF EXISTS (SELECT * FROM Progress WHERE AchievementID = @achievementId AND UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
UPDATE Progress SET Progress = @progress WHERE AchievementID = @achievementId AND UserID = @userId AND PlatformID = @platformId
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
INSERT INTO Progress VALUES (@userId, @platformId, @achievementId, @progress)
|
||||
END
|
||||
RETURN 0
|
||||
GO
|
||||
|
60
sql/SearchProcs.sql
Normal file
60
sql/SearchProcs.sql
Normal file
|
@ -0,0 +1,60 @@
|
|||
-----------------------
|
||||
-- SEARCH ACHIEVEMENTS --
|
||||
-----------------------
|
||||
|
||||
CREATE PROCEDURE SearchAchievements(
|
||||
@searchTerm VARCHAR(32),
|
||||
@userId INT,
|
||||
@completed BIT,
|
||||
@minCompletion FLOAT,
|
||||
@maxCompletion FLOAT,
|
||||
@minDifficulty FLOAT,
|
||||
@maxDifficulty FLOAT,
|
||||
@minQuality FLOAT,
|
||||
@maxQuality FLOAT
|
||||
)
|
||||
AS
|
||||
IF @userId IS NULL AND @completed = 1
|
||||
BEGIN
|
||||
PRINT 'Cannot search for completed achievements with no user specified'
|
||||
RETURN 1
|
||||
END
|
||||
|
||||
IF @searchTerm IS NULL OR @searchTerm = ''
|
||||
SET @searchTerm = '%'
|
||||
ELSE
|
||||
SET @searchTerm = '%' + @searchTerm + '%'
|
||||
PRINT @searchTerm
|
||||
|
||||
IF NOT @userId IS NULL
|
||||
SELECT TOP 100 Game.[Name] AS Game, Achievement.[Name], Completion, Difficulty, Quality
|
||||
FROM Achievement
|
||||
JOIN MaxProgress ON AchievementID = Achievement.ID AND UserID = @userId
|
||||
JOIN Game ON Game.ID = GameID
|
||||
JOIN AchievementCompletion AC ON AC.ID = Achievement.ID
|
||||
JOIN AchievementRatings AR ON AR.ID = Achievement.ID
|
||||
WHERE (Game.[Name] LIKE @searchTerm OR Achievement.[Name] LIKE @searchTerm)
|
||||
AND (@completed <> 1 OR Progress = Stages )
|
||||
AND (@minCompletion IS NULL OR @minCompletion <= Completion)
|
||||
AND (@maxCompletion IS NULL OR @maxCompletion >= Completion)
|
||||
AND (@minDifficulty IS NULL OR @minDifficulty <= Difficulty)
|
||||
AND (@maxDifficulty IS NULL OR @maxDifficulty >= Difficulty)
|
||||
AND (@minQuality IS NULL OR @minQuality <= Quality )
|
||||
AND (@maxQuality IS NULL OR @maxQuality >= Quality )
|
||||
ELSE
|
||||
SELECT TOP 100 Achievement.ID, Game.[Name] AS Game, Achievement.[Name], Completion, Quality, Difficulty
|
||||
FROM Achievement
|
||||
JOIN Game ON Game.ID = GameID
|
||||
JOIN AchievementCompletion AC ON AC.ID = Achievement.ID
|
||||
JOIN AchievementRatings AR ON AR.ID = Achievement.ID
|
||||
WHERE (Game.[Name] LIKE @searchTerm OR Achievement.[Name] LIKE @searchTerm)
|
||||
AND (@minCompletion IS NULL OR @minCompletion <= Completion)
|
||||
AND (@maxCompletion IS NULL OR @maxCompletion >= Completion)
|
||||
AND (@minDifficulty IS NULL OR @minDifficulty <= Difficulty)
|
||||
AND (@maxDifficulty IS NULL OR @maxDifficulty >= Difficulty)
|
||||
AND (@minQuality IS NULL OR @minQuality <= Quality )
|
||||
AND (@maxQuality IS NULL OR @maxQuality >= Quality )
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
EXEC SearchAchievements '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
|
|
@ -27,6 +27,9 @@
|
|||
|
||||
-----------------------------
|
||||
|
||||
--CREATE TYPE ImageType FROM VARCHAR(4) NULL
|
||||
--GO
|
||||
|
||||
CREATE TABLE [User] (
|
||||
ID INT IDENTITY(0, 1) NOT NULL,
|
||||
Email VARCHAR(254) NOT NULL,
|
||||
|
@ -36,7 +39,9 @@ CREATE TABLE [User] (
|
|||
Hue INT NOT NULL
|
||||
CONSTRAINT HueDefault DEFAULT 0
|
||||
CONSTRAINT HueConstraint CHECK (0 <= Hue AND Hue <= 360),
|
||||
PFP VARCHAR(11) NULL,
|
||||
ProfileImage ImageType,
|
||||
[Admin] BIT NOT NULL
|
||||
CONSTRAINT AdmivDefault DEFAULT 0,
|
||||
Verified BIT NOT NULL
|
||||
CONSTRAINT VerifiedDefault DEFAULT 0
|
||||
PRIMARY KEY(ID)
|
||||
|
@ -44,14 +49,15 @@ CREATE TABLE [User] (
|
|||
|
||||
CREATE TABLE [Platform] (
|
||||
ID INT IDENTITY(0, 1) NOT NULL,
|
||||
PlatformName VARCHAR(32) NOT NULL
|
||||
PlatformName VARCHAR(32) NOT NULL,
|
||||
Icon ImageType
|
||||
PRIMARY KEY(ID)
|
||||
)
|
||||
|
||||
CREATE TABLE [Game] (
|
||||
ID INT IDENTITY(0, 1) NOT NULL,
|
||||
Name VARCHAR(32) NOT NULL,
|
||||
Thumbnail VARCHAR(256) NULL
|
||||
Icon ImageType
|
||||
PRIMARY KEY(ID)
|
||||
)
|
||||
|
||||
|
@ -61,7 +67,7 @@ CREATE TABLE [Achievement] (
|
|||
Name VARCHAR(128) NOT NULL,
|
||||
Description VARCHAR(512) NULL,
|
||||
Stages INT NOT NULL,
|
||||
Thumbnail VARCHAR(256) NULL
|
||||
Icon ImageType
|
||||
PRIMARY KEY(ID)
|
||||
FOREIGN KEY(GameID) REFERENCES [Game](ID)
|
||||
ON UPDATE CASCADE
|
||||
|
@ -89,7 +95,7 @@ CREATE TABLE [Progress] (
|
|||
PlatformID INT NOT NULL,
|
||||
AchievementID INT NOT NULL,
|
||||
Progress INT NOT NULL
|
||||
PRIMARY KEY(UserID, AchievementID)
|
||||
PRIMARY KEY(UserID, PlatformID, AchievementID)
|
||||
FOREIGN KEY(UserID) REFERENCES [User](ID)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
@ -117,7 +123,7 @@ CREATE TABLE [IsOn] (
|
|||
CREATE TABLE [ExistsOn] (
|
||||
GameID INT NOT NULL,
|
||||
PlatformID INT NOT NULL,
|
||||
PlatformGameID INT NOT NULL
|
||||
PlatformGameID VARCHAR(32) NOT NULL
|
||||
PRIMARY KEY(GameID, PlatformID)
|
||||
FOREIGN KEY(GameID) REFERENCES [Game](ID)
|
||||
ON UPDATE CASCADE
|
186
sql/UserData.sql
186
sql/UserData.sql
|
@ -1,186 +0,0 @@
|
|||
---------------------------------------
|
||||
-- GET USER NAME AND STATS PROCEDURE --
|
||||
---------------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserNameAndStats(
|
||||
@userId INT,
|
||||
@username VARCHAR(32) OUTPUT,
|
||||
@completed INT OUTPUT,
|
||||
@average INT OUTPUT,
|
||||
@perfect INT OUTPUT
|
||||
)
|
||||
AS
|
||||
BEGIN TRANSACTION
|
||||
|
||||
SELECT @username = Username
|
||||
FROM [User]
|
||||
WHERE ID = @userId
|
||||
|
||||
IF @username IS NULL
|
||||
BEGIN
|
||||
PRINT 'No user found with specified id'
|
||||
ROLLBACK TRANSACTION
|
||||
RETURN 1
|
||||
END
|
||||
|
||||
DECLARE @progress TABLE (GameID INT, Completed INT, Total INT)
|
||||
INSERT INTO @progress
|
||||
SELECT GameID, SUM(CASE WHEN Progress.Progress = Achievement.Stages THEN 1 ELSE 0 END) AS Completed, COUNT(AchievementID) AS Total
|
||||
FROM Achievement
|
||||
JOIN Progress ON
|
||||
Progress.UserID = @userId
|
||||
AND Progress.AchievementID = Achievement.ID
|
||||
GROUP BY GameID
|
||||
COMMIT TRANSACTION
|
||||
|
||||
SELECT @completed = SUM(Completed)
|
||||
FROM @progress
|
||||
|
||||
SELECT @average = AVG((Completed * 100) / Total)
|
||||
FROM @progress
|
||||
|
||||
SELECT @perfect = COUNT(GameID)
|
||||
FROM @progress
|
||||
WHERE Completed = Total
|
||||
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
----------------------------------
|
||||
-- GET USER PLATFORMS PROCEDURE --
|
||||
----------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserPlatforms(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
SELECT [Platform].ID, [PlatformName], (CASE WHEN UserID IS NOT NULL THEN 1 ELSE 0 END) AS Connected
|
||||
FROM [Platform]
|
||||
LEFT JOIN IsOn ON IsOn.PlatformID = [Platform].ID
|
||||
ORDER BY [Platform].ID
|
||||
GO
|
||||
|
||||
--------------------------------
|
||||
-- GET USER RATINGS PROCEDURE --
|
||||
--------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserRatings(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
SELECT Game.Name AS GameName, Achievement.Name AS AchievementName, Quality, Difficulty, Rating.[Description]
|
||||
FROM Rating
|
||||
JOIN Achievement ON Achievement.ID = Rating.AchievementID
|
||||
JOIN Game ON Game.ID = Achievement.GameID
|
||||
WHERE UserID = @userId
|
||||
GO
|
||||
|
||||
------------------------------
|
||||
-- GET USER IMAGE PROCEDURE --
|
||||
------------------------------
|
||||
|
||||
CREATE PROCEDURE GetUserImage(
|
||||
@userId INT
|
||||
)
|
||||
AS
|
||||
IF NOT EXISTS (SELECT * FROM [User] WHERE ID = @userId)
|
||||
BEGIN
|
||||
PRINT 'No user with specified ID found'
|
||||
RETURN 1
|
||||
END
|
||||
SELECT PFP FROM [User] WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------
|
||||
-- SET USERNAME --
|
||||
------------------
|
||||
|
||||
CREATE PROCEDURE SetUsername(
|
||||
@userId INT,
|
||||
@username VARCHAR(32)
|
||||
)
|
||||
AS
|
||||
IF NOT EXISTS (SELECT * FROM [User] WHERE ID = @userId)
|
||||
BEGIN
|
||||
PRINT 'No user with specified ID found'
|
||||
RETURN 1
|
||||
END
|
||||
UPDATE [User] SET Username = @username WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
------------------------------
|
||||
-- SET USER IMAGE PROCEDURE --
|
||||
------------------------------
|
||||
|
||||
CREATE PROCEDURE SetUserImage(
|
||||
@userId INT,
|
||||
@type VARCHAR(11)
|
||||
)
|
||||
AS
|
||||
IF NOT EXISTS (SELECT * FROM [User] WHERE ID = @userId)
|
||||
BEGIN
|
||||
PRINT 'No user with specified ID found'
|
||||
RETURN 1
|
||||
END
|
||||
UPDATE [User] SET PFP = @type WHERE ID = @userId
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
---------------------------
|
||||
-- ADD USER TO PROCEDURE --
|
||||
---------------------------
|
||||
|
||||
CREATE PROCEDURE AddPlatform(
|
||||
@userId INT,
|
||||
@platformId INT,
|
||||
@platformUserID VARCHAR(32)
|
||||
)
|
||||
AS
|
||||
IF NOT EXISTS (SELECT * FROM [User] WHERE ID = @userId)
|
||||
BEGIN
|
||||
PRINT 'No user with specified ID found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM [Platform] WHERE ID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'No platform with specified ID found'
|
||||
RETURN 2
|
||||
END
|
||||
IF EXISTS (SELECT * FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'User already exists on platform'
|
||||
RETURN 3
|
||||
END
|
||||
INSERT INTO IsOn VALUES (@userId, @platformId, @platformUserId)
|
||||
RETURN 0
|
||||
GO
|
||||
|
||||
--------------------------------
|
||||
-- REMOVE USER FROM PROCEDURE --
|
||||
--------------------------------
|
||||
|
||||
CREATE PROCEDURE RemovePlatform(
|
||||
@userId INT,
|
||||
@platformId INT
|
||||
)
|
||||
AS
|
||||
IF NOT EXISTS (SELECT * FROM [User] WHERE ID = @userId)
|
||||
BEGIN
|
||||
PRINT 'No user with specified ID found'
|
||||
RETURN 1
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM [Platform] WHERE ID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'No platform with specified ID found'
|
||||
RETURN 2
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId)
|
||||
BEGIN
|
||||
PRINT 'User does not exist on platform'
|
||||
RETURN 3
|
||||
END
|
||||
DELETE FROM IsOn WHERE UserID = @userId AND PlatformID = @platformId
|
||||
RETURN 0
|
||||
GO
|
35
sql/Views.sql
Normal file
35
sql/Views.sql
Normal file
|
@ -0,0 +1,35 @@
|
|||
-- The maximum progress a user has on an achievement across all platforms
|
||||
CREATE VIEW MaxProgress
|
||||
AS
|
||||
SELECT UserID, AchievementID, MAX(Progress) AS Progress
|
||||
FROM Progress
|
||||
GROUP BY UserID, AchievementID
|
||||
GO
|
||||
|
||||
-- List of games and users with the number of completed achievements out of the total achievements the user has completed
|
||||
CREATE VIEW GameCompletionByUser
|
||||
AS
|
||||
SELECT UserID, GameID, SUM(CASE WHEN Progress = Stages THEN 1 ELSE 0 END) AS Completed, COUNT(AchievementID) AS Total
|
||||
FROM Achievement
|
||||
JOIN MaxProgress ON AchievementID = Achievement.ID
|
||||
GROUP BY UserID, GameID
|
||||
GO
|
||||
|
||||
-- List of achievements and the percentage of people who have completed it
|
||||
CREATE VIEW AchievementCompletion
|
||||
AS
|
||||
SELECT Achievement.ID, (CASE WHEN COUNT(UserID) = 0 THEN NULL ELSE (SUM(CASE WHEN Progress = Stages THEN 1 ELSE 0 END) * 100 / COUNT(UserID)) END) AS Completion
|
||||
FROM Achievement
|
||||
LEFT JOIN MaxProgress ON AchievementID = Achievement.ID
|
||||
GROUP BY Achievement.ID
|
||||
GO
|
||||
|
||||
-- List of achievements and their average quality and difficulty ratings filling with null as necessary
|
||||
CREATE VIEW AchievementRatings
|
||||
AS
|
||||
SELECT Achievement.ID, AVG(Quality) AS Quality, AVG(Difficulty) AS Difficulty
|
||||
FROM Achievement
|
||||
LEFT JOIN Rating ON AchievementID = Achievement.ID
|
||||
GROUP BY Achievement.ID
|
||||
GO
|
||||
|
Loading…
Reference in a new issue