diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5133e6d..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -tmp/* -sql/CreateBackendUser.sql diff --git a/backend/.gitignore b/backend/.gitignore index 99a4769..ef5b413 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -22,6 +22,3 @@ src/main/resources/application-local.properties # Server Keystore src/main/resources/achievements-ssl-key.p12 - -# Program Data -storage/ \ No newline at end of file diff --git a/backend/src/main/java/achievements/Application.java b/backend/src/main/java/achievements/Application.java index 81dd0b3..b622eff 100644 --- a/backend/src/main/java/achievements/Application.java +++ b/backend/src/main/java/achievements/Application.java @@ -1,27 +1,20 @@ package achievements; -import achievements.misc.DbConnection; +import achievements.services.DbConnectionService; 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 +@SpringBootApplication public class Application { public static void main(String[] args) { var context = SpringApplication.run(Application.class, args); // Verify the database connection succeeded - var db = context.getBean(DbConnection.class); + var db = context.getBean(DbConnectionService.class); if (db.getConnection() == null) { SpringApplication.exit(context, () -> 0); } @@ -32,15 +25,10 @@ public class Application { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { - registry - .addMapping("/**") - .allowedOrigins("*"); + registry + .addMapping("/*") + .allowedOrigins("*"); } }; } - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } } \ No newline at end of file diff --git a/backend/src/main/java/achievements/apis/PlatformAPI.java b/backend/src/main/java/achievements/apis/PlatformAPI.java deleted file mode 100644 index f241a60..0000000 --- a/backend/src/main/java/achievements/apis/PlatformAPI.java +++ /dev/null @@ -1,17 +0,0 @@ -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); -} diff --git a/backend/src/main/java/achievements/apis/SteamAPI.java b/backend/src/main/java/achievements/apis/SteamAPI.java deleted file mode 100644 index 3bbe0d5..0000000 --- a/backend/src/main/java/achievements/apis/SteamAPI.java +++ /dev/null @@ -1,113 +0,0 @@ -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(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(); - try { - 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()); - // Technically this is not the advertised logo url, but it's used be steamcommunity.com - // and steamdb.info and it gives better aspect ratios and it means I don't need the random - // logo_url field - newGame.setThumbnail("https://cdn.cloudflare.steamstatic.com/steam/apps/" + game.getAppid() + "/header.jpg"); - newGame.setPlayed(game.getPlaytime_forever() > 0); - - var achievements = new HashMap(); - - var gameSchemaUrl = gameSchemaBaseUrl.cloneBuilder() - .queryParam("appid", game.getAppid()) - .toUriString(); - var playerAchievementsUrl = playerAchievementsBaseUrl.cloneBuilder() - .queryParam("appid", game.getAppid()) - .toUriString(); - - try { - 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); - } - } - } catch (Exception e) { - System.err.println("Forbidden APPID: " + game.getAppid()); - e.printStackTrace(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - var response = new APIResponse(); - response.setGames(games); - return response; - } -} diff --git a/backend/src/main/java/achievements/apis/steam/GetOwnedGameBody.java b/backend/src/main/java/achievements/apis/steam/GetOwnedGameBody.java deleted file mode 100644 index e34f7ed..0000000 --- a/backend/src/main/java/achievements/apis/steam/GetOwnedGameBody.java +++ /dev/null @@ -1,96 +0,0 @@ -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 games; - - public int getGame_count() { - return game_count; - } - - public void setGame_count(int game_count) { - this.game_count = game_count; - } - - public List getGames() { - return games; - } - - public void setGames(List games) { - this.games = games; - } - } - - @JsonProperty("response") - private Response response; - - public Response getResponse() { - return response; - } - - public void setResponse(Response response) { - this.response = response; - } -} diff --git a/backend/src/main/java/achievements/apis/steam/GetPlayerAchievementsBody.java b/backend/src/main/java/achievements/apis/steam/GetPlayerAchievementsBody.java deleted file mode 100644 index c6c899b..0000000 --- a/backend/src/main/java/achievements/apis/steam/GetPlayerAchievementsBody.java +++ /dev/null @@ -1,54 +0,0 @@ -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 achievements; - - public List getAchievements() { - return achievements; - } - - public void setAchievements(List achievements) { - this.achievements = achievements; - } - } - - @JsonProperty("playerstats") - private PlayerStats playerstats; - - public PlayerStats getPlayerstats() { - return playerstats; - } - - public void setPlayerstats(PlayerStats playerstats) { - this.playerstats = playerstats; - } -} diff --git a/backend/src/main/java/achievements/apis/steam/GetSchemaForGameBody.java b/backend/src/main/java/achievements/apis/steam/GetSchemaForGameBody.java deleted file mode 100644 index a4f1a4d..0000000 --- a/backend/src/main/java/achievements/apis/steam/GetSchemaForGameBody.java +++ /dev/null @@ -1,87 +0,0 @@ -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 achievements; - - public List getAchievements() { - return achievements; - } - - public void setAchievements(List 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; - } -} diff --git a/backend/src/main/java/achievements/controllers/AchievementController.java b/backend/src/main/java/achievements/controllers/AchievementController.java deleted file mode 100644 index c523d59..0000000 --- a/backend/src/main/java/achievements/controllers/AchievementController.java +++ /dev/null @@ -1,60 +0,0 @@ -package achievements.controllers; - -import achievements.data.APError; -import achievements.data.request.RateAchievement; -import achievements.services.ImageService; -import achievements.services.AchievementService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletResponse; - -@RestController -@RequestMapping("/achievement") -public class AchievementController { - - @Autowired - private AchievementService achievementService; - @Autowired - private ImageService imageService; - - @GetMapping(value = "/{achievement}", produces = "application/json") - public ResponseEntity getAchievement(@PathVariable("achievement") int achievementId) { - var achievement = achievementService.getAchievement(achievementId); - if (achievement == null) { - return ResponseEntity.badRequest().body(new APError(1, "Failed to get achievement")); - } else { - return ResponseEntity.ok(achievement); - } - } - - @GetMapping(value = "/{achievement}/image") - public void getProfilePicture(@PathVariable("achievement") int achievement, HttpServletResponse response) { - var icon = achievementService.getIcon(achievement); - imageService.send(icon, "achievement", response); - } - - @GetMapping(value = "/{achievement}/rating/{user}") - public ResponseEntity getRating(@PathVariable("achievement") int achievement, @PathVariable("user") int user) { - var rating = achievementService.getRating(achievement, user); - if (rating == null) { - return ResponseEntity.badRequest().body("{}"); - } else { - return ResponseEntity.ok(rating); - } - } - - @PostMapping(value = "/{achievement}/rating/{user}") - public ResponseEntity setRating(@PathVariable("achievement") int achievement, @PathVariable("user") int user, @RequestBody RateAchievement rating) { - var review = achievementService.setRating(achievement, user, rating); - if (review == null) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("{}"); - } else if (review.getSessionKey() == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(review); - } else { - return ResponseEntity.status(HttpStatus.CREATED).body("{}"); - } - } -} diff --git a/backend/src/main/java/achievements/controllers/AuthController.java b/backend/src/main/java/achievements/controllers/AuthController.java deleted file mode 100644 index 988f6ef..0000000 --- a/backend/src/main/java/achievements/controllers/AuthController.java +++ /dev/null @@ -1,87 +0,0 @@ -package achievements.controllers; - -import achievements.data.APError; -import achievements.data.Session; -import achievements.data.User; -import achievements.services.AuthenticationService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -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.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/auth") -public class AuthController { - - @Autowired - private AuthenticationService authService; - - /** - * Acceptable codes - * 0 => Success - * 1 => Email already registered - * - * -1 => Unknown error - */ - @PostMapping(value = "/create_user", consumes = "application/json", produces = "application/json") - public ResponseEntity createUser(@RequestBody User user) { - var response = authService.createUser(user); - if (response.status == 0) { - return ResponseEntity.status(HttpStatus.CREATED).body(response.session); - } else if (response.status > 0) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new APError(response.status)); - } else { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new APError(response.status)); - } - } - - /** - * DO NOT RETURN CODE DIRECTLY! - * - * User should only ever recieve -1, 0, or 1. The specific authentication error should be hidden. - * - * Acceptable codes - * 0 => Success - * 1 => Unregistered email address - * 2 => Incorrect password - * - * -1 => Unknown error - */ - @PostMapping(value = "/login", consumes = "application/json", produces = "application/json") - public ResponseEntity login(@RequestBody User user) { - var response = authService.login(user); - if (response.status == 0) { - return ResponseEntity.ok(response.session); - } else if (response.status > 0) { - // Hardcoded 1 response code - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new APError(1)); - } else { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new APError(response.status)); - } - } - - @PostMapping(value = "/refresh", consumes = "application/json", produces = "application/json") - public ResponseEntity refresh(@RequestBody Session key) { - if (key.getId() == -1) { - if (authService.openAuth()) { - if (authService.refresh(key)) { - return ResponseEntity.ok(key); - } else { - return ResponseEntity.ok(authService.session().generate(-1, 0, true)); - } - } - } else if (authService.refresh(key)) { - return ResponseEntity.ok(key); - } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{}"); - } - - @PostMapping(value = "/logout", consumes = "application/json") - public ResponseEntity logout(@RequestBody Session session) { - authService.logout(session); - return ResponseEntity.ok("{}"); - } -} diff --git a/backend/src/main/java/achievements/controllers/Controller.java b/backend/src/main/java/achievements/controllers/Controller.java new file mode 100644 index 0000000..7595ba5 --- /dev/null +++ b/backend/src/main/java/achievements/controllers/Controller.java @@ -0,0 +1,65 @@ +package achievements.controllers; + +import achievements.data.Achievements; +import achievements.data.Games; +import achievements.data.InternalError; +import achievements.services.DbService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@RestController +public class Controller { + + @Autowired + private DbService db; + + public Controller() {} + + @RequestMapping(value = { "/achievements", "/achievements/{Name}" }, method = GET, produces = "application/json") + public ResponseEntity fetchAchievements(@PathVariable(value = "Name", required = false) String getName) { + var achievements = (Achievements) null; + if (getName == null) { + achievements = db.getAchievements("%"); + } else { + achievements = db.getAchievements(getName); + } + var mapper = new ObjectMapper(); + try { + if (achievements == null) { + return new ResponseEntity(mapper.writeValueAsString(new InternalError("Could not get achievements from database")), HttpStatus.INTERNAL_SERVER_ERROR); + } else { + return new ResponseEntity(mapper.writeValueAsString(achievements), HttpStatus.OK); + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + return new ResponseEntity("{}", HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @RequestMapping(value = { "/games", "/games/{Name}" }, method = GET, produces = "application/json") + public ResponseEntity fetchGames(@PathVariable(value = "Name", required = false) String getName) { + var games = (Games) null; + if (getName == null) { + games = db.getGames("%"); + } else { + games = db.getGames(getName); + } + var mapper = new ObjectMapper(); + try { + if (games == null) { + return new ResponseEntity(mapper.writeValueAsString(new InternalError("Could not get games from database")), HttpStatus.INTERNAL_SERVER_ERROR); + } else { + return new ResponseEntity(mapper.writeValueAsString(games), HttpStatus.OK); + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + return new ResponseEntity("{}", HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/backend/src/main/java/achievements/controllers/GameController.java b/backend/src/main/java/achievements/controllers/GameController.java deleted file mode 100644 index e8e7662..0000000 --- a/backend/src/main/java/achievements/controllers/GameController.java +++ /dev/null @@ -1,27 +0,0 @@ -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); - } -} diff --git a/backend/src/main/java/achievements/controllers/ImportController.java b/backend/src/main/java/achievements/controllers/ImportController.java deleted file mode 100644 index 6e2b7d4..0000000 --- a/backend/src/main/java/achievements/controllers/ImportController.java +++ /dev/null @@ -1,47 +0,0 @@ -package achievements.controllers; - -import achievements.data.importing.ImportPlatform; -import achievements.data.importing.ImportUser; -import achievements.data.importing.ImportUserPlatform; -import achievements.services.ImportService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/import") -public class ImportController { - - @Autowired - private ImportService importService; - - @PostMapping(value = "/platform", consumes = "application/json", produces = "application/json") - public ResponseEntity createPlatform(@RequestBody ImportPlatform platform) { - var response = importService.importPlatform(platform); - if (response == 0) { - return ResponseEntity.ok("{}"); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } - - @PostMapping(value = "/user", consumes = "application/json", produces = "application/json") - public ResponseEntity createUser(@RequestBody ImportUser user) { - var response = importService.importUser(user); - if (response == 0) { - return ResponseEntity.ok("{}"); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } - - @PostMapping(value = "/user/platform", consumes = "application/json", produces = "application/json") - public ResponseEntity addUserToPlatform(@RequestBody ImportUserPlatform userPlatform) { - var response = importService.importUserPlatform(userPlatform); - if (response == 0) { - return ResponseEntity.ok("{}"); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } -} diff --git a/backend/src/main/java/achievements/controllers/PlatformController.java b/backend/src/main/java/achievements/controllers/PlatformController.java deleted file mode 100644 index f37a5c4..0000000 --- a/backend/src/main/java/achievements/controllers/PlatformController.java +++ /dev/null @@ -1,27 +0,0 @@ -package achievements.controllers; - -import achievements.services.ImageService; -import achievements.services.PlatformService; -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("/platform") -public class PlatformController { - - @Autowired - private ImageService imageService; - @Autowired - private PlatformService platformService; - - @GetMapping(value = "/{platform}/image") - public void getIcon(@PathVariable("platform") int platform, HttpServletResponse response) { - var icon = platformService.getIcon(platform); - imageService.send(icon, "platform", response); - } -} diff --git a/backend/src/main/java/achievements/controllers/SearchController.java b/backend/src/main/java/achievements/controllers/SearchController.java deleted file mode 100644 index 5539435..0000000 --- a/backend/src/main/java/achievements/controllers/SearchController.java +++ /dev/null @@ -1,48 +0,0 @@ -package achievements.controllers; - -import achievements.data.request.SearchAchievements; -import achievements.data.request.SearchGames; -import achievements.data.request.SearchUsers; -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("[]"); - } - } - - @PostMapping(value = "/users", consumes = "application/json", produces = "application/json") - public ResponseEntity searchAchievements(@RequestBody SearchUsers searchUsers) { - var users = searchService.searchUsers(searchUsers); - if (users != null) { - return ResponseEntity.ok(users); - } else { - return ResponseEntity.badRequest().body("[]"); - } - } - - @PostMapping(value = "/games", consumes = "application/json", produces = "application/json") - public ResponseEntity searchAchievements(@RequestBody SearchGames searchGames) { - var users = searchService.searchGames(searchGames); - if (users != null) { - return ResponseEntity.ok(users); - } else { - return ResponseEntity.badRequest().body("[]"); - } - } -} diff --git a/backend/src/main/java/achievements/controllers/UserController.java b/backend/src/main/java/achievements/controllers/UserController.java deleted file mode 100644 index 84e060e..0000000 --- a/backend/src/main/java/achievements/controllers/UserController.java +++ /dev/null @@ -1,102 +0,0 @@ -package achievements.controllers; - -import achievements.data.APError; -import achievements.data.APPostRequest; -import achievements.data.request.AddPlatform; -import achievements.data.request.RemovePlatform; -import achievements.data.request.SetUsername; -import achievements.services.ImageService; -import achievements.services.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import javax.servlet.http.HttpServletResponse; - -@RestController -@RequestMapping("/user") -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); - if (profile == null) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new APError(1, "Failed to get user profile")); - } else { - return ResponseEntity.ok(profile); - } - } - - @PostMapping(value = "/{user}/username", consumes = "application/json", produces = "application/json") - public ResponseEntity setUsername(@PathVariable("user") int userId, @RequestBody SetUsername username) { - var name = userService.setUsername(userId, username); - if (name == 0) { - return ResponseEntity.status(HttpStatus.CREATED).body("{}"); - } - return ResponseEntity.badRequest().body("{}"); - } - - @GetMapping(value = "/{user}/image") - public void getProfilePicture(@PathVariable("user") int user, HttpServletResponse response) { - 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.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 ("success".equals(type)) { - return ResponseEntity.status(HttpStatus.CREATED).body("{ \"code\": 0, \"message\": \"Success\" }"); - } - - } catch (Exception e) { - e.printStackTrace(); - } - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{ \"code\": -1, \"message\": \"Unknown error\" }"); - } - - @PostMapping(value = "/{user}/platforms/add", consumes = "application/json", produces = "application/json") - public ResponseEntity addPlatformForUser(@PathVariable("user") int userId, @RequestBody AddPlatform request) { - var result = userService.addPlatform(userId, request, true); - if (result == 0) { - return ResponseEntity.status(HttpStatus.CREATED).body("{}"); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } - - @PostMapping(value = "/{user}/platforms/remove", consumes = "application/json", produces = "application/json") - 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("{}"); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } - - @GetMapping(value = "/{user}/noteworthy", produces = "application/json") - public ResponseEntity getNoteworthy(@PathVariable("user") int userId) { - var result = userService.getNoteworthy(userId); - if (result != null) { - return ResponseEntity.ok(result); - } else { - return ResponseEntity.badRequest().body("{}"); - } - } -} diff --git a/backend/src/main/java/achievements/data/APError.java b/backend/src/main/java/achievements/data/APError.java deleted file mode 100644 index b6f8df2..0000000 --- a/backend/src/main/java/achievements/data/APError.java +++ /dev/null @@ -1,36 +0,0 @@ -package achievements.data; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class APError { - - @JsonProperty("code") - private int code; - @JsonProperty("message") - private String message; - - public APError(int code) { - this.code = code; - } - - public APError(int code, String message) { - this.code = code; - this.message = message; - } - - public int getCode() { - return code; - } - - public void setCode(int code) { - this.code = code; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } -} diff --git a/backend/src/main/java/achievements/data/APIResponse.java b/backend/src/main/java/achievements/data/APIResponse.java deleted file mode 100644 index 01136e8..0000000 --- a/backend/src/main/java/achievements/data/APIResponse.java +++ /dev/null @@ -1,126 +0,0 @@ -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 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 getAchievements() { - return achievements; - } - - public void setAchievements(List achievements) { - this.achievements = achievements; - } - } - - @JsonProperty("games") - private List games; - - public List getGames() { - return games; - } - - public void setGames(List games) { - this.games = games; - } -} diff --git a/backend/src/main/java/achievements/data/APPostRequest.java b/backend/src/main/java/achievements/data/APPostRequest.java deleted file mode 100644 index fcf1b25..0000000 --- a/backend/src/main/java/achievements/data/APPostRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package achievements.data; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class APPostRequest { - - @JsonProperty("key") - private String key; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } -} diff --git a/backend/src/main/java/achievements/data/Achievements.java b/backend/src/main/java/achievements/data/Achievements.java new file mode 100644 index 0000000..4b2033f --- /dev/null +++ b/backend/src/main/java/achievements/data/Achievements.java @@ -0,0 +1,64 @@ +package achievements.data; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; + +public class Achievements { + + public static class Achievement { + + @JsonProperty("name") + private String name; + @JsonProperty("description") + private String description; + @JsonProperty("stages") + private int stages; + + public Achievement(String name, String description, int stages) { + this.name = name; + this.description = description; + this.stages = stages; + } + + // Start Getters/Setters + 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; } + // End Getters/Setters + } + + @JsonProperty("gameID") + private int gameID; + @JsonProperty("gameName") + private String gameName; + @JsonProperty("achievements") + private List achievements; + + public Achievements() { achievements = new ArrayList(); } + + // Start Getters/Setters + public int getGameID() { return gameID; } + + public void setGameID(int gameID) { this.gameID = gameID; } + + public String getGameName() { return gameName; } + + public void setGameName(String gameName) { this.gameName = gameName; } + + public List getAchievements() { return achievements; } + + public void setAchievements(List achievements) { this.achievements = achievements; } + // End Getters/Setters + + public void addAchievement(Achievement achievement) { this.achievements.add(achievement); }; +} diff --git a/backend/src/main/java/achievements/data/Games.java b/backend/src/main/java/achievements/data/Games.java new file mode 100644 index 0000000..57ab9c6 --- /dev/null +++ b/backend/src/main/java/achievements/data/Games.java @@ -0,0 +1,57 @@ +package achievements.data; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; + +public class Games { + + public static class Game { + + @JsonProperty("ID") + private int id; + @JsonProperty("name") + private String name; + @JsonProperty("platforms") + private List platforms; + + public Game(int id, String name, String platform) { + this.id = id; + this.name = name; + this.platforms = new ArrayList<>(); + this.platforms.add(platform); + } + + // Start Getters/Setters + public int getId() { return id; } + + public void setId(int id) { this.id = id; } + + public String getName() { return name; } + + public void setName(String name) { this.name = name; } + + public List getPlatforms() { return platforms; } + + public void setPlatforms(List platforms) { this.platforms = platforms; } + + public void addToPlatforms(String platform) { this.platforms.add(platform); } + // End Getters/Setters + + } + + @JsonProperty("games") + private List games; + + public Games() { games = new ArrayList(); } + + // Start Getters/Setters + public List getGames() { return games; } + + public void setGames(List games) { this.games = games; } + // End Getters/Setters + + public void addGame(Game game) { this.games.add(game); } + +} diff --git a/backend/src/main/java/achievements/data/InternalError.java b/backend/src/main/java/achievements/data/InternalError.java new file mode 100644 index 0000000..16486a5 --- /dev/null +++ b/backend/src/main/java/achievements/data/InternalError.java @@ -0,0 +1,21 @@ +package achievements.data; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class InternalError { + + @JsonProperty + private String message; + + public InternalError(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/backend/src/main/java/achievements/data/Profile.java b/backend/src/main/java/achievements/data/Profile.java deleted file mode 100644 index 2c02d38..0000000 --- a/backend/src/main/java/achievements/data/Profile.java +++ /dev/null @@ -1,166 +0,0 @@ -package achievements.data; - -import achievements.data.response.search.Achievement; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class Profile { - - public static class Platform { - @JsonProperty - private int id; - @JsonProperty("name") - private String name; - @JsonProperty("connected") - private boolean connected; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean getConnected() { - return connected; - } - - public void setConnected(boolean connected) { - this.connected = connected; - } - } - - public static class Rating { - @JsonProperty("achievementId") - private int achievementId; - @JsonProperty("name") - private String name; - @JsonProperty("difficulty") - private Float difficulty; - @JsonProperty("quality") - private Float quality; - @JsonProperty("review") - private String review; - - public int getAchievementId() { - return achievementId; - } - - public void setAchievementId(int achievementId) { - this.achievementId = achievementId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Float getDifficulty() { - return difficulty; - } - - public void setDifficulty(Float difficulty) { - this.difficulty = difficulty; - } - - public Float getQuality() { - return quality; - } - - public void setQuality(Float quality) { - this.quality = quality; - } - - public String getReview() { - return review; - } - - public void setReview(String review) { - this.review = review; - } - } - - @JsonProperty("username") - private String username; - @JsonProperty("completed") - private int completed; - @JsonProperty("average") - private Integer average; - @JsonProperty("perfect") - private int perfect; - @JsonProperty("noteworthy") - private List noteworthy; - @JsonProperty("platforms") - private List platforms; - @JsonProperty("ratings") - private List ratings; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public int getCompleted() { - return completed; - } - - public void setCompleted(int completed) { - this.completed = completed; - } - - public Integer getAverage() { - return average; - } - - public void setAverage(Integer average) { - this.average = average; - } - - public int getPerfect() { - return perfect; - } - - public void setPerfect(int perfect) { - this.perfect = perfect; - } - - public List getNoteworthy() { - return noteworthy; - } - - public void setNoteworthy(List noteworthy) { - this.noteworthy = noteworthy; - } - - public List getPlatforms() { - return platforms; - } - - public void setPlatforms(List platforms) { - this.platforms = platforms; - } - - public List getRatings() { - return ratings; - } - - public void setRatings(List ratings) { - this.ratings = ratings; - } -} diff --git a/backend/src/main/java/achievements/data/Session.java b/backend/src/main/java/achievements/data/Session.java deleted file mode 100644 index a5c5e2f..0000000 --- a/backend/src/main/java/achievements/data/Session.java +++ /dev/null @@ -1,58 +0,0 @@ -package achievements.data; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Session { - - @JsonProperty("key") - private String key; - @JsonProperty("id") - private int id; - @JsonProperty("hue") - private int hue; - @JsonProperty("admin") - private boolean admin; - @JsonIgnore - private boolean used; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getHue() { - return hue; - } - - public void setHue(int hue) { - this.hue = hue; - } - - public boolean isAdmin() { - return admin; - } - - public void setAdmin(boolean admin) { - this.admin = admin; - } - - public boolean isUsed() { - return used; - } - - public void setUsed(boolean used) { - this.used = used; - } -} diff --git a/backend/src/main/java/achievements/data/User.java b/backend/src/main/java/achievements/data/User.java deleted file mode 100644 index 065de1d..0000000 --- a/backend/src/main/java/achievements/data/User.java +++ /dev/null @@ -1,37 +0,0 @@ -package achievements.data; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class User { - - @JsonProperty("email") - public String email; - @JsonProperty("username") - public String username; - @JsonProperty("password") - public String password; - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/backend/src/main/java/achievements/data/importing/ImportPlatform.java b/backend/src/main/java/achievements/data/importing/ImportPlatform.java deleted file mode 100644 index fa24903..0000000 --- a/backend/src/main/java/achievements/data/importing/ImportPlatform.java +++ /dev/null @@ -1,37 +0,0 @@ -package achievements.data.importing; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class ImportPlatform { - - @JsonProperty("userId") - private int userId; - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("name") - private String name; - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/backend/src/main/java/achievements/data/importing/ImportUser.java b/backend/src/main/java/achievements/data/importing/ImportUser.java deleted file mode 100644 index c46bedf..0000000 --- a/backend/src/main/java/achievements/data/importing/ImportUser.java +++ /dev/null @@ -1,38 +0,0 @@ -package achievements.data.importing; - -import achievements.data.User; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class ImportUser extends User { - - @JsonProperty("userId") - private int userId; - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("admin") - private boolean admin; - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public boolean isAdmin() { - return admin; - } - - public void setAdmin(boolean admin) { - this.admin = admin; - } -} diff --git a/backend/src/main/java/achievements/data/importing/ImportUserPlatform.java b/backend/src/main/java/achievements/data/importing/ImportUserPlatform.java deleted file mode 100644 index b43519a..0000000 --- a/backend/src/main/java/achievements/data/importing/ImportUserPlatform.java +++ /dev/null @@ -1,28 +0,0 @@ -package achievements.data.importing; - -import achievements.data.request.AddPlatform; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class ImportUserPlatform extends AddPlatform { - - @JsonProperty("userId") - private int userId; - @JsonProperty("userEmail") - private String userEmail; - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public String getUserEmail() { - return userEmail; - } - - public void setUserEmail(String userEmail) { - this.userEmail = userEmail; - } -} diff --git a/backend/src/main/java/achievements/data/request/AddPlatform.java b/backend/src/main/java/achievements/data/request/AddPlatform.java deleted file mode 100644 index 7e8f352..0000000 --- a/backend/src/main/java/achievements/data/request/AddPlatform.java +++ /dev/null @@ -1,36 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AddPlatform { - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("platformId") - private int platformId; - @JsonProperty("platformUserId") - private String platformUserId; - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public int getPlatformId() { - return platformId; - } - - public void setPlatformId(int platformId) { - this.platformId = platformId; - } - - public String getPlatformUserId() { - return platformUserId; - } - - public void setPlatformUserId(String platformUserId) { - this.platformUserId = platformUserId; - } -} diff --git a/backend/src/main/java/achievements/data/request/RateAchievement.java b/backend/src/main/java/achievements/data/request/RateAchievement.java deleted file mode 100644 index d38cbf4..0000000 --- a/backend/src/main/java/achievements/data/request/RateAchievement.java +++ /dev/null @@ -1,47 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class RateAchievement { - - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("difficulty") - private Float difficulty; - @JsonProperty("quality") - private Float quality; - @JsonProperty("review") - private String review; - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public Float getDifficulty() { - return difficulty; - } - - public void setDifficulty(Float difficulty) { - this.difficulty = difficulty; - } - - public Float getQuality() { - return quality; - } - - public void setQuality(Float quality) { - this.quality = quality; - } - - public String getReview() { - return review; - } - - public void setReview(String review) { - this.review = review; - } -} diff --git a/backend/src/main/java/achievements/data/request/RemovePlatform.java b/backend/src/main/java/achievements/data/request/RemovePlatform.java deleted file mode 100644 index ff6371e..0000000 --- a/backend/src/main/java/achievements/data/request/RemovePlatform.java +++ /dev/null @@ -1,26 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class RemovePlatform { - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("platformId") - private int platformId; - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public int getPlatformId() { - return platformId; - } - - public void setPlatformId(int platformId) { - this.platformId = platformId; - } -} diff --git a/backend/src/main/java/achievements/data/request/SearchAchievements.java b/backend/src/main/java/achievements/data/request/SearchAchievements.java deleted file mode 100644 index 5dbd340..0000000 --- a/backend/src/main/java/achievements/data/request/SearchAchievements.java +++ /dev/null @@ -1,117 +0,0 @@ -package achievements.data.request; - -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; - @JsonProperty("ordering") - private String ordering; - @JsonProperty("orderDirection") - private String orderDirection; - - 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; - } - - public String getOrdering() { - return ordering; - } - - public void setOrdering(String ordering) { - this.ordering = ordering; - } - - public String getOrderDirection() { - return orderDirection; - } - - public void setOrderDirection(String orderDirection) { - this.orderDirection = orderDirection; - } -} diff --git a/backend/src/main/java/achievements/data/request/SearchGames.java b/backend/src/main/java/achievements/data/request/SearchGames.java deleted file mode 100644 index b066b69..0000000 --- a/backend/src/main/java/achievements/data/request/SearchGames.java +++ /dev/null @@ -1,117 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class SearchGames { - - @JsonProperty("searchTerm") - private String searchTerm; - @JsonProperty("userId") - private Integer userId; - @JsonProperty("owned") - private boolean owned; - @JsonProperty("minAvgCompletion") - private Float minAvgCompletion; - @JsonProperty("maxAvgCompletion") - private Float maxAvgCompletion; - @JsonProperty("minNumOwners") - private Float minNumOwners; - @JsonProperty("maxNumOwners") - private Float maxNumOwners; - @JsonProperty("minNumPerfects") - private Float minNumPerfects; - @JsonProperty("maxNumPerfects") - private Float maxNumPerfects; - @JsonProperty("ordering") - private String ordering; - @JsonProperty("orderDirection") - private String orderDirection; - - 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 isOwned() { - return owned; - } - - public void setOwned(boolean owned) { - this.owned = owned; - } - - public Float getMinAvgCompletion() { - return minAvgCompletion; - } - - public void setMinAvgCompletion(Float minAvgCompletion) { - this.minAvgCompletion = minAvgCompletion; - } - - public Float getMaxAvgCompletion() { - return maxAvgCompletion; - } - - public void setMaxAvgCompletion(Float maxAvgCompletion) { - this.maxAvgCompletion = maxAvgCompletion; - } - - public Float getMinNumOwners() { - return minNumOwners; - } - - public void setMinNumOwners(Float minNumOwners) { - this.minNumOwners = minNumOwners; - } - - public Float getMaxNumOwners() { - return maxNumOwners; - } - - public void setMaxNumOwners(Float maxNumOwners) { - this.maxNumOwners = maxNumOwners; - } - - public Float getMinNumPerfects() { - return minNumPerfects; - } - - public void setMinNumPerfects(Float minNumPerfects) { - this.minNumPerfects = minNumPerfects; - } - - public Float getMaxNumPerfects() { - return maxNumPerfects; - } - - public void setMaxNumPerfects(Float maxNumPerfects) { - this.maxNumPerfects = maxNumPerfects; - } - - public String getOrdering() { - return ordering; - } - - public void setOrdering(String ordering) { - this.ordering = ordering; - } - - public String getOrderDirection() { - return orderDirection; - } - - public void setOrderDirection(String orderDirection) { - this.orderDirection = orderDirection; - } -} diff --git a/backend/src/main/java/achievements/data/request/SearchUsers.java b/backend/src/main/java/achievements/data/request/SearchUsers.java deleted file mode 100644 index bb11c82..0000000 --- a/backend/src/main/java/achievements/data/request/SearchUsers.java +++ /dev/null @@ -1,97 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class SearchUsers { - - @JsonProperty("searchTerm") - private String searchTerm; - @JsonProperty("minOwned") - private Float minOwned; - @JsonProperty("maxOwned") - private Float maxOwned; - @JsonProperty("minCompleted") - private Float minCompleted; - @JsonProperty("maxCompleted") - private Float maxCompleted; - @JsonProperty("minAvgCompletion") - private Float minAvgCompletion; - @JsonProperty("maxAvgCompletion") - private Float maxAvgCompletion; - @JsonProperty("ordering") - private String ordering; - @JsonProperty("orderDirection") - private String orderDirection; - - public String getSearchTerm() { - return searchTerm; - } - - public void setSearchTerm(String searchTerm) { - this.searchTerm = searchTerm; - } - - public Float getMinOwned() { - return minOwned; - } - - public void setMinOwned(Float minOwned) { - this.minOwned = minOwned; - } - - public Float getMaxOwned() { - return maxOwned; - } - - public void setMaxOwned(Float maxOwned) { - this.maxOwned = maxOwned; - } - - public Float getMinCompleted() { - return minCompleted; - } - - public void setMinCompleted(Float minCompleted) { - this.minCompleted = minCompleted; - } - - public Float getMaxCompleted() { - return maxCompleted; - } - - public void setMaxCompleted(Float maxCompleted) { - this.maxCompleted = maxCompleted; - } - - public Float getMinAvgCompletion() { - return minAvgCompletion; - } - - public void setMinAvgCompletion(Float minAvgCompletion) { - this.minAvgCompletion = minAvgCompletion; - } - - public Float getMaxAvgCompletion() { - return maxAvgCompletion; - } - - public void setMaxAvgCompletion(Float maxAvgCompletion) { - this.maxAvgCompletion = maxAvgCompletion; - } - - public String getOrdering() { - return ordering; - } - - public void setOrdering(String ordering) { - this.ordering = ordering; - } - - public String getOrderDirection() { - return orderDirection; - } - - public void setOrderDirection(String orderDirection) { - this.orderDirection = orderDirection; - } -} diff --git a/backend/src/main/java/achievements/data/request/SetUsername.java b/backend/src/main/java/achievements/data/request/SetUsername.java deleted file mode 100644 index 96274aa..0000000 --- a/backend/src/main/java/achievements/data/request/SetUsername.java +++ /dev/null @@ -1,36 +0,0 @@ -package achievements.data.request; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class SetUsername { - @JsonProperty("sessionKey") - private String sessionKey; - @JsonProperty("userId") - private int userId; - @JsonProperty("username") - private String username; - - public String getSessionKey() { - return sessionKey; - } - - public void setSessionKey(String sessionKey) { - this.sessionKey = sessionKey; - } - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } -} diff --git a/backend/src/main/java/achievements/data/response/search/Achievement.java b/backend/src/main/java/achievements/data/response/search/Achievement.java deleted file mode 100644 index fa8969e..0000000 --- a/backend/src/main/java/achievements/data/response/search/Achievement.java +++ /dev/null @@ -1,134 +0,0 @@ -package achievements.data.response.search; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class Achievement { - - public static class Rating { - @JsonProperty("userId") - private int userId; - @JsonProperty("username") - private String username; - @JsonProperty("difficulty") - private Float difficulty; - @JsonProperty("quality") - private Float quality; - @JsonProperty("review") - private String review; - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public Float getDifficulty() { - return difficulty; - } - - public void setDifficulty(Float difficulty) { - this.difficulty = difficulty; - } - - public Float getQuality() { - return quality; - } - - public void setQuality(Float quality) { - this.quality = quality; - } - - public String getReview() { - return review; - } - - public void setReview(String review) { - this.review = review; - } - } - - @JsonProperty("ID") - private int ID; - @JsonProperty("game") - private String game; - @JsonProperty("name") - private String name; - @JsonProperty("description") - private String description; - @JsonProperty("completion") - private Integer completion; - @JsonProperty("difficulty") - private Float difficulty; - @JsonProperty("quality") - private Float quality; - @JsonProperty("ratings") - private List ratings; - - public int getID() { - return ID; - } - - public void setID(int ID) { - this.ID = ID; - } - - public String getGame() { - return game; - } - - public void setGame(String game) { - this.game = game; - } - - 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 Integer getCompletion() { - return completion; - } - - public void setCompletion(Integer completion) { - this.completion = completion; - } - - public Float getDifficulty() { - return difficulty; - } - - public void setDifficulty(Float difficulty) { - this.difficulty = difficulty; - } - - public Float getQuality() { - return quality; - } - - public void setQuality(Float quality) { - this.quality = quality; - } - - public List getRatings() { - return ratings; - } - - public void setRatings(List ratings) { - this.ratings = ratings; - } -} diff --git a/backend/src/main/java/achievements/data/response/search/Game.java b/backend/src/main/java/achievements/data/response/search/Game.java deleted file mode 100644 index c113d19..0000000 --- a/backend/src/main/java/achievements/data/response/search/Game.java +++ /dev/null @@ -1,59 +0,0 @@ -package achievements.data.response.search; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Game { - - @JsonProperty("ID") - private int ID; - @JsonProperty("name") - private String name; - @JsonProperty("achievement_count") - private int achievement_count; - @JsonProperty("avg_completion") - private Integer avg_completion; - @JsonProperty("num_owners") - private int num_owners; - @JsonProperty("num_perfects") - private int num_perfects; - - public int getID() { return ID; } - - public void setID(int ID) { this.ID = ID; } - - public String getName() { return name; } - - public void setName(String name) { this.name = name; } - - public int getAchievement_count() { - return achievement_count; - } - - public void setAchievement_count(int achievement_count) { - this.achievement_count = achievement_count; - } - - public Integer getAvg_completion() { - return avg_completion; - } - - public void setAvg_completion(Integer avg_completion) { - this.avg_completion = avg_completion; - } - - public int getNum_owners() { - return num_owners; - } - - public void setNum_owners(int num_owners) { - this.num_owners = num_owners; - } - - public int getNum_perfects() { - return num_perfects; - } - - public void setNum_perfects(int num_perfects) { - this.num_perfects = num_perfects; - } -} diff --git a/backend/src/main/java/achievements/data/response/search/User.java b/backend/src/main/java/achievements/data/response/search/User.java deleted file mode 100644 index d5bd718..0000000 --- a/backend/src/main/java/achievements/data/response/search/User.java +++ /dev/null @@ -1,67 +0,0 @@ -package achievements.data.response.search; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class User { - - @JsonProperty("ID") - private int ID; - @JsonProperty("username") - private String username; - @JsonProperty("game_count") - private int game_count; - @JsonProperty("achievement_count") - private int achievement_count; - @JsonProperty("avg_completion") - private Integer avg_completion; - @JsonProperty("perfect_games") - private int perfect_games; - - public int getID() { - return ID; - } - - public void setID(int ID) { - this.ID = ID; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public int getGame_count() { - return game_count; - } - - public void setGame_count(int game_count) { - this.game_count = game_count; - } - - public int getAchievement_count() { - return achievement_count; - } - - public void setAchievement_count(int achievement_count) { - this.achievement_count = achievement_count; - } - - public Integer getAvg_completion() { - return avg_completion; - } - - public void setAvg_completion(Integer avg_completion) { - this.avg_completion = avg_completion; - } - - public int getPerfect_games() { - return perfect_games; - } - - public void setPerfect_games(int perfect_games) { - this.perfect_games = perfect_games; - } -} diff --git a/backend/src/main/java/achievements/misc/APIList.java b/backend/src/main/java/achievements/misc/APIList.java deleted file mode 100644 index 99ea3d1..0000000 --- a/backend/src/main/java/achievements/misc/APIList.java +++ /dev/null @@ -1,41 +0,0 @@ -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 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)); - } -} diff --git a/backend/src/main/java/achievements/misc/HashManager.java b/backend/src/main/java/achievements/misc/HashManager.java deleted file mode 100644 index 2e133c4..0000000 --- a/backend/src/main/java/achievements/misc/HashManager.java +++ /dev/null @@ -1,70 +0,0 @@ -package achievements.misc; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Random; - -public class HashManager { - - private static final Random RANDOM = new SecureRandom(); - - public static byte[] hash(byte[] salt, byte[] password) { - try { - var concat = new byte[salt.length + password.length]; - int i = 0; - for (; i < salt.length; ++i) { - concat[i] = salt[i]; - } - for (int j = 0; j < password.length; ++j) { - concat[i + j] = password[j]; - } - - var md = MessageDigest.getInstance("SHA-256"); - return md.digest(concat); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - return null; - } - - public static String encode(byte[] bytes) { - var chars = new char[bytes.length << 1]; - for (int i = 0; i < bytes.length; ++i) { - chars[(i << 1) ] = toHex(bytes[i] >> 0); - chars[(i << 1) + 1] = toHex(bytes[i] >> 4); - } - return new String(chars); - } - - public static byte[] decode(String data) { - var decoded = new byte[data.length() >> 1]; - for (int i = 0; i < data.length(); i += 2) { - int currentByte = - (fromHex(data.charAt(i )) ) | - (fromHex(data.charAt(i + 1)) << 4); - decoded[i >> 1] = (byte) (currentByte & 0xFF); - } - return decoded; - } - - public static byte[] generateBytes(int length) { - var bytes = new byte[length]; - RANDOM.nextBytes(bytes); - return bytes; - } - - public static char toHex(int halfByte) { - halfByte = halfByte & 0xF; - if (0 <= halfByte && halfByte <= 9 ) return (char) (halfByte + '0' ); - if (10 <= halfByte && halfByte <= 15) return (char) (halfByte + 'a' - 10); - return '0'; - } - - public static int fromHex(char c) { - if ('0' <= c && c <= '9') return c - '0'; - if ('A' <= c && c <= 'F') return c - 'A' + 10; - if ('a' <= c && c <= 'f') return c - 'a' + 10; - return 0; - } -} diff --git a/backend/src/main/java/achievements/misc/Password.java b/backend/src/main/java/achievements/misc/Password.java deleted file mode 100644 index f96958d..0000000 --- a/backend/src/main/java/achievements/misc/Password.java +++ /dev/null @@ -1,33 +0,0 @@ -package achievements.misc; - -public class Password { - - public final String salt; - public final String hash; - - private Password(String salt, String hash) { - this.salt = salt; - this.hash = hash; - } - - public static Password generate(String password) { - // Generate the salt - var salt = HashManager.generateBytes(16); // 128 bits - - return new Password( - HashManager.encode(salt), - HashManager.encode(HashManager.hash(salt, password.getBytes())) - ); - } - - public static boolean validate(String salt, String password, String hash) { - var srcHash = HashManager.hash(HashManager.decode(salt), password.getBytes()); - var targetHash = HashManager.decode(hash); - for (int i = 0; i < srcHash.length; ++i) { - if (srcHash[i] != targetHash[i]) { - return false; - } - } - return true; - } -} diff --git a/backend/src/main/java/achievements/misc/SessionManager.java b/backend/src/main/java/achievements/misc/SessionManager.java deleted file mode 100644 index 9f01c42..0000000 --- a/backend/src/main/java/achievements/misc/SessionManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package achievements.misc; - -import achievements.data.Session; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.HashMap; - -@Component -public class SessionManager { - - private HashMap sessions; - - public SessionManager() { - sessions = new HashMap<>(); - } - - public Session generate(int user, int hue, boolean admin) { - var key = HashManager.encode(HashManager.generateBytes(16)); - var session = new Session(); - session.setKey(key); - session.setId(user); - session.setHue(hue); - session.setAdmin(admin); - sessions.put(key, session); - return session; - } - - public int getUser(String key) { - return sessions.get(key).getId(); - } - - public void remove(String key) { - sessions.remove(key); - } - - public boolean validate(int user, String key) { - 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) { - var foreign = sessions.get(key); - if (foreign != null) { - foreign.setUsed(true); - return true; - } else { - return false; - } - } - - // Clean up inactive sessions - @Scheduled(cron = "0 */30 * * * *") - public void clean() { - var remove = new ArrayList(); - sessions.forEach((key, session) -> { - if (!session.isUsed()) { - remove.add(session.getKey()); - } else { - session.setUsed(false); - } - }); - for (var session : remove) { - sessions.remove(session); - } - } -} diff --git a/backend/src/main/java/achievements/services/APIService.java b/backend/src/main/java/achievements/services/APIService.java deleted file mode 100644 index 14d1ed3..0000000 --- a/backend/src/main/java/achievements/services/APIService.java +++ /dev/null @@ -1,114 +0,0 @@ -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; -import java.util.HashSet; - -@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 AddGameToUser(?, ?, ?)}"); - 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, game.getPlatformGameId()); - 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(); - - var set = new HashSet(); - 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); - set.add(achievementId); - - 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; - } -} diff --git a/backend/src/main/java/achievements/services/AchievementService.java b/backend/src/main/java/achievements/services/AchievementService.java deleted file mode 100644 index 06cb554..0000000 --- a/backend/src/main/java/achievements/services/AchievementService.java +++ /dev/null @@ -1,146 +0,0 @@ -package achievements.services; - -import achievements.data.request.RateAchievement; -import achievements.data.response.search.Achievement; -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.Types; -import java.util.ArrayList; - -@Service -public class AchievementService { - - @Autowired - private DbConnection dbs; - private Connection db; - - @Autowired - private ImageService imageService; - - @Autowired - private AuthenticationService authService; - - @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; - } - - public Achievement getAchievement(int achievementId) { - try { - var stmt = db.prepareCall("{call GetAchievement(?)}"); - stmt.setInt(1, achievementId); - - var result = stmt.executeQuery(); - if (result.next()) { - var achievement = new Achievement(); - achievement.setID(result.getInt("ID")); - achievement.setName(result.getString("Name")); - achievement.setCompletion(result.getInt("Completion")); if (result.wasNull()) { achievement.setCompletion(null); } - achievement.setDescription(result.getString("Description")); - achievement.setDifficulty(result.getFloat("Difficulty")); if (result.wasNull()) { achievement.setDifficulty(null); } - achievement.setQuality(result.getFloat("Quality")); if (result.wasNull()) { achievement.setQuality(null); } - - stmt = db.prepareCall("{call GetRatingsForAchievement(?)}"); - stmt.setInt(1, achievementId); - - var ratings = new ArrayList(); - var results = stmt.executeQuery(); - while (results.next()) { - var rating = new Achievement.Rating(); - rating.setUserId(results.getInt("UserID")); - rating.setUsername(results.getString("Username")); - rating.setDifficulty(results.getFloat("Difficulty")); if (results.wasNull()) { rating.setDifficulty(null); } - rating.setQuality(results.getFloat("Quality")); if (results.wasNull()) { rating.setQuality(null); } - rating.setReview(results.getString("Description")); - ratings.add(rating); - } - achievement.setRatings(ratings); - return achievement; - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public RateAchievement getRating(int achievement, int user) { - try { - var stmt = db.prepareCall("{call HasProgress(?, ?, ?)}"); - stmt.setInt(1, user); - stmt.setInt(2, achievement); - stmt.registerOutParameter(3, Types.BOOLEAN); - - stmt.execute(); - if (stmt.getBoolean(3)) { - stmt = db.prepareCall("{call GetRating(?, ?)}"); - stmt.setInt(1, user); - stmt.setInt(2, achievement); - - var result = stmt.executeQuery(); - if (result.next()) { - var rating = new RateAchievement(); - rating.setDifficulty(result.getFloat("Difficulty")); if (result.wasNull()) { rating.setDifficulty(null); } - rating.setQuality(result.getFloat("Quality")); if (result.wasNull()) { rating.setQuality(null); } - rating.setReview(result.getString("Description")); - return rating; - } else { - return new RateAchievement(); - } - } else { - return null; - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public RateAchievement setRating(int achievementId, int userId, RateAchievement rateAchievement) { - if (authService.session().validate(userId, rateAchievement.getSessionKey())) { - try { - var stmt = db.prepareCall("{call SetRating(?, ?, ?, ?, ?)}"); - stmt.setInt(1, userId); - stmt.setInt(2, achievementId); - stmt.setFloat(3, rateAchievement.getDifficulty()); - stmt.setFloat(4, rateAchievement.getQuality()); - stmt.setString(5, rateAchievement.getReview()); - - stmt.execute(); - return rateAchievement; - } catch (Exception e) { - e.printStackTrace(); - } - } - try { - var stmt = db.prepareCall("{call GetRating(?, ?)}"); - stmt.setInt(1, userId); - stmt.setInt(2, achievementId); - - var result = stmt.executeQuery(); - if (result.next()) { - var rating = new RateAchievement(); - rating.setDifficulty(result.getFloat("Difficulty")); - rating.setQuality(result.getFloat("Quality")); - rating.setReview(result.getString("Review")); - return rating; - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/backend/src/main/java/achievements/services/AuthenticationService.java b/backend/src/main/java/achievements/services/AuthenticationService.java deleted file mode 100644 index e2387db..0000000 --- a/backend/src/main/java/achievements/services/AuthenticationService.java +++ /dev/null @@ -1,141 +0,0 @@ -package achievements.services; - -import achievements.data.Session; -import achievements.data.User; -import achievements.misc.DbConnection; -import achievements.misc.Password; -import achievements.misc.SessionManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; -import java.sql.*; - -@Service -public class AuthenticationService { - - public static class LoginResponse { - public int status; - public Session session; - - public LoginResponse() { - this.status = 0; - } - - public LoginResponse(int status) { - this.status = status; - this.session = null; - } - - public LoginResponse(int status, Session session) { - this.status = status; - this.session = session; - } - } - - @Autowired - private DbConnection dbs; - private Connection db; - - @Autowired - private SessionManager session; - - @PostConstruct - private void init() { - db = dbs.getConnection(); - } - - public LoginResponse createUser(User user) { - if (!user.getEmail().matches(".+@\\w+\\.\\w+")) { - return new LoginResponse(2); - } - - try { - var statement = db.prepareCall("{? = call CreateUser(?, ?, ?, ?, ?, ?)}"); - statement.registerOutParameter(1, Types.INTEGER); - statement.setString(2, user.getEmail()); - statement.setString(3, user.getUsername()); - - var password = Password.generate(user.getPassword()); - statement.setString(4, password.salt); - statement.setString(5, password.hash); - - statement.registerOutParameter(6, Types.INTEGER); - statement.registerOutParameter(7, Types.INTEGER); - - statement.execute(); - var response = new LoginResponse( - statement.getInt(1), - session.generate( - statement.getInt(6), - statement.getInt(7), - false - ) - ); - statement.close(); - - return response; - } catch (SQLException e) { - e.printStackTrace(); - } - return new LoginResponse(-1); - } - - public LoginResponse login(User user) { - var response = new LoginResponse(-1); - try { - var statement = db.prepareCall("{? = call GetUserLogin(?)}"); - statement.registerOutParameter(1, Types.INTEGER); - statement.setString(2, user.email); - - statement.execute(); - if (statement.getInt(1) == 0) { - var result = statement.executeQuery(); - result.next(); - var salt = result.getString("Salt"); - var hash = result.getString("Password"); - if (Password.validate(salt, user.getPassword(), hash)) { - response = new LoginResponse( - 0, - session.generate( - result.getInt("ID"), - result.getInt("Hue"), - result.getBoolean("Admin") - ) - ); - } else { - response = new LoginResponse(2); - } - } else { - response = new LoginResponse(1); - } - statement.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return response; - } - - public boolean refresh(Session key) { return session.refresh(key.getKey()); } - - public boolean openAuth() { - try { - var stmt = db.prepareCall("{call HasUser(?)}"); - stmt.registerOutParameter(1, Types.BOOLEAN); - - stmt.execute(); - return !stmt.getBoolean(1); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - public void logout(Session key) { - session.remove(key.getKey()); - } - - public SessionManager session() { - return session; - } -} diff --git a/backend/src/main/java/achievements/misc/DbConnection.java b/backend/src/main/java/achievements/services/DbConnectionService.java similarity index 88% rename from backend/src/main/java/achievements/misc/DbConnection.java rename to backend/src/main/java/achievements/services/DbConnectionService.java index 1d7ea59..99ef0b1 100644 --- a/backend/src/main/java/achievements/misc/DbConnection.java +++ b/backend/src/main/java/achievements/services/DbConnectionService.java @@ -1,56 +1,56 @@ -package achievements.misc; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.sql.Connection; -import java.sql.SQLException; -import com.microsoft.sqlserver.jdbc.SQLServerDataSource; - -@Component -public class DbConnection { - - private Connection connection; - - @Value("${database.server}") - private String serverName; - @Value("${database.name}") - private String databaseName; - @Value("${database.user.name}") - private String username; - @Value("${database.user.password}") - private String password; - - public DbConnection() {} - - @PostConstruct - public void connect() { - try { - var dataSource = new SQLServerDataSource(); - dataSource.setServerName (serverName ); - dataSource.setDatabaseName(databaseName); - dataSource.setUser (username ); - dataSource.setPassword (password ); - connection = dataSource.getConnection(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Connection getConnection() { - return this.connection; - } - - @PreDestroy - public void disconnect() { - try { - if (connection != null) { - connection.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } -} +package achievements.services; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.sql.Connection; +import java.sql.SQLException; +import com.microsoft.sqlserver.jdbc.SQLServerDataSource; + +@Component +public class DbConnectionService { + + private Connection connection; + + @Value("${database.server}") + private String serverName; + @Value("${database.name}") + private String databaseName; + @Value("${database.user.name}") + private String username; + @Value("${database.user.password}") + private String password; + + public DbConnectionService() {} + + @PostConstruct + public void connect() { + try { + var dataSource = new SQLServerDataSource(); + dataSource.setServerName (serverName ); + dataSource.setDatabaseName(databaseName); + dataSource.setUser (username ); + dataSource.setPassword (password ); + connection = dataSource.getConnection(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public Connection getConnection() { + return this.connection; + } + + @PreDestroy + public void disconnect() { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/backend/src/main/java/achievements/services/DbService.java b/backend/src/main/java/achievements/services/DbService.java new file mode 100644 index 0000000..20eca43 --- /dev/null +++ b/backend/src/main/java/achievements/services/DbService.java @@ -0,0 +1,87 @@ +package achievements.services; + +import achievements.data.Achievements; +import achievements.data.Games; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.sql.*; + +@Service +public class DbService { + + @Autowired + private DbConnectionService dbs; + private Connection db; + + @PostConstruct + private void init() { db = dbs.getConnection(); } + + public Achievements getAchievements(String gameName) { + try { + // Create Query + CallableStatement stmt = db.prepareCall("{? = call GetAchievements(?)}"); + stmt.registerOutParameter(1, Types.INTEGER); + stmt.setString(2, gameName); + + // Read Result(s) + ResultSet results = stmt.executeQuery(); + var achievements = new Achievements(); + while (results.next()) { + // Add Result(s) to data class + int achievementGameID = results.getInt("GameID"); + String achievementGameName = results.getString("GameName"); + String achievementName = results.getString("Name"); + String achievementDescription = results.getString("Description"); + int achievementStages = results.getInt("Stages"); + // Checks if getting from specific game or all achievements + if (!gameName.equals("%")) { + achievements.setGameID(achievementGameID); + achievements.setGameName(achievementGameName); + } + achievements.addAchievement(new Achievements.Achievement(achievementName, achievementDescription, achievementStages)); + } + stmt.close(); + return achievements; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + public Games getGames(String name) { + try { + // Create Query + CallableStatement stmt = db.prepareCall("{? = call GetGame(?)}"); + stmt.registerOutParameter(1, Types.INTEGER); + stmt.setString(2, name); + + // Read Result(s) + ResultSet results = stmt.executeQuery(); + var games = new Games(); + while (results.next()) { + // Add Result(s) to data class + int gameID = results.getInt("ID"); + String gameName = results.getString("Name"); + String gamePlatform = results.getString("PlatformName"); + if (!games.getGames().isEmpty()) { + var lastGame = games.getGames().get(games.getGames().size()-1); + if (lastGame.getId() == gameID) { + lastGame.addToPlatforms(gamePlatform); + } else { + games.addGame(new Games.Game(gameID,gameName,gamePlatform)); + } + } else { + games.addGame(new Games.Game(gameID,gameName,gamePlatform)); + } + + } + stmt.close(); + return games; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/backend/src/main/java/achievements/services/GameService.java b/backend/src/main/java/achievements/services/GameService.java deleted file mode 100644 index cd03c0d..0000000 --- a/backend/src/main/java/achievements/services/GameService.java +++ /dev/null @@ -1,34 +0,0 @@ -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 GetGameIcon(?)}"); - return imageService.getImageType(stmt, gameId); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/backend/src/main/java/achievements/services/ImageService.java b/backend/src/main/java/achievements/services/ImageService.java deleted file mode 100644 index 2acf4a8..0000000 --- a/backend/src/main/java/achievements/services/ImageService.java +++ /dev/null @@ -1,80 +0,0 @@ -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 MIME_TO_EXT = new HashMap<>(); - public static final HashMap 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); - } -} diff --git a/backend/src/main/java/achievements/services/ImportService.java b/backend/src/main/java/achievements/services/ImportService.java deleted file mode 100644 index f2bf8b7..0000000 --- a/backend/src/main/java/achievements/services/ImportService.java +++ /dev/null @@ -1,81 +0,0 @@ -package achievements.services; - -import achievements.data.importing.ImportPlatform; -import achievements.data.importing.ImportUser; -import achievements.data.importing.ImportUserPlatform; -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.sql.Types; - -@Service -public class ImportService { - - @Autowired - private DbConnection dbs; - private Connection db; - - @Autowired - private AuthenticationService authService; - @Autowired - private UserService userService; - - @PostConstruct - public void init() { - db = dbs.getConnection(); - } - - public int importPlatform(ImportPlatform platform) { - if (authService.session().validateAdmin(platform.getUserId(), platform.getSessionKey())) { - try { - var stmt = db.prepareCall("{call AddPlatform(?, ?)}"); - stmt.setString(1, platform.getName()); - stmt.registerOutParameter(2, Types.INTEGER); - - stmt.execute(); - return 0; - } catch (SQLException e) { - e.printStackTrace(); - } - } - return -1; - } - - public int importUser(ImportUser user) { - if (authService.session().validateAdmin(user.getUserId(), user.getSessionKey())) { - try { - var response = authService.createUser(user); - if (user.isAdmin()) { - var stmt = db.prepareCall("{call OpUser(?)}"); - stmt.setInt(1, response.session.getId()); - stmt.execute(); - } - - return 0; - } catch (SQLException e) { - e.printStackTrace(); - } - } - return -1; - } - - public int importUserPlatform(ImportUserPlatform userPlatform) { - if (authService.session().validateAdmin(userPlatform.getUserId(), userPlatform.getSessionKey())) { - try { - var stmt = db.prepareCall("{call GetIdFromEmail(?, ?)}"); - stmt.setString(1, userPlatform.getUserEmail()); - stmt.registerOutParameter(2, Types.INTEGER); - - stmt.execute(); - return userService.addPlatform(stmt.getInt(2), userPlatform, false); - } catch (Exception e) { - e.printStackTrace(); - } - } - return -1; - } -} diff --git a/backend/src/main/java/achievements/services/PlatformService.java b/backend/src/main/java/achievements/services/PlatformService.java deleted file mode 100644 index ad1ad2e..0000000 --- a/backend/src/main/java/achievements/services/PlatformService.java +++ /dev/null @@ -1,34 +0,0 @@ -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 PlatformService { - - @Autowired - 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; - } -} diff --git a/backend/src/main/java/achievements/services/SearchService.java b/backend/src/main/java/achievements/services/SearchService.java deleted file mode 100644 index aa3ec03..0000000 --- a/backend/src/main/java/achievements/services/SearchService.java +++ /dev/null @@ -1,132 +0,0 @@ -package achievements.services; - -import achievements.data.request.SearchGames; -import achievements.data.request.SearchUsers; -import achievements.data.response.search.Achievement; -import achievements.data.request.SearchAchievements; -import achievements.data.response.search.Game; -import achievements.data.response.search.User; -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.*; - -@Service -public class SearchService { - - @Autowired - private DbConnection dbs; - private Connection db; - - @PostConstruct - private void init() { - db = dbs.getConnection(); - } - - public List 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); } - stmt.setString(10, query.getOrdering()); - stmt.setString(11, query.getOrderDirection()); - var results = stmt.executeQuery(); - - var achievements = new ArrayList(); - 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; - } - - public List searchUsers(SearchUsers query) { - try { - var stmt = db.prepareCall("{call SearchUsers(?, ?, ?, ?, ?, ?, ?, ?, ?)}"); - stmt.setString(1, query.getSearchTerm()); - if (query.getMinOwned() != null) { stmt.setFloat(2, query.getMinOwned()); } else { stmt.setString(2, null); } - if (query.getMaxOwned() != null) { stmt.setFloat(3, query.getMaxOwned()); } else { stmt.setString(3, null); } - if (query.getMinCompleted() != null) { stmt.setFloat(4, query.getMinCompleted()); } else { stmt.setString(4, null); } - if (query.getMaxCompleted() != null) { stmt.setFloat(5, query.getMaxCompleted()); } else { stmt.setString(5, null); } - if (query.getMinAvgCompletion() != null) { stmt.setFloat(6, query.getMinAvgCompletion()); } else { stmt.setString(6, null); } - if (query.getMaxAvgCompletion() != null) { stmt.setFloat(7, query.getMaxAvgCompletion()); } else { stmt.setString(7, null); } - stmt.setString(8, query.getOrdering()); - stmt.setString(9, query.getOrderDirection()); - var results = stmt.executeQuery(); - - var users = new ArrayList(); - while (results.next()) { - var user = new User(); - user.setID (results.getInt ("ID" )); - user.setUsername (results.getString("Username" )); - user.setGame_count (results.getInt ("GameCount" )); - user.setAchievement_count(results.getInt ("AchievementCount")); - user.setAvg_completion (results.getInt ("AvgCompletion" )); if (results.wasNull()) { user.setAvg_completion(null); } - user.setPerfect_games (results.getInt ("PerfectGames" )); - users.add(user); - } - - return users; - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List searchGames(SearchGames query) { - try { - var stmt = db.prepareCall("{call SearchGames(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}"); - stmt.setString(1, query.getSearchTerm()); - stmt.setBoolean(3, query.isOwned()); - if (query.getUserId() != null) { stmt.setInt (2, query.getUserId()); } else { stmt.setString(2, null); } - if (query.getMinAvgCompletion() != null) { stmt.setFloat(4, query.getMinAvgCompletion()); } else { stmt.setString(4, null); } - if (query.getMaxAvgCompletion() != null) { stmt.setFloat(5, query.getMaxAvgCompletion()); } else { stmt.setString(5, null); } - if (query.getMinNumOwners() != null) { stmt.setFloat(6, query.getMinNumOwners()); } else { stmt.setString(6, null); } - if (query.getMaxNumOwners() != null) { stmt.setFloat(7, query.getMaxNumOwners()); } else { stmt.setString(7, null); } - if (query.getMinNumPerfects() != null) { stmt.setFloat(8, query.getMinNumPerfects()); } else { stmt.setString(8, null); } - if (query.getMaxNumPerfects() != null) { stmt.setFloat(9, query.getMaxNumPerfects()); } else { stmt.setString(9, null); } - stmt.setString(10, query.getOrdering()); - stmt.setString(11, query.getOrderDirection()); - var results = stmt.executeQuery(); - - var games = new ArrayList(); - while (results.next()) { - var game = new Game(); - game.setID (results.getInt ("ID" )); - game.setName (results.getString("Name" )); - game.setAchievement_count(results.getInt ("AchievementCount")); - game.setAvg_completion (results.getInt ("AvgCompletion" )); if (results.wasNull()) { game.setAvg_completion(null); } - game.setNum_owners (results.getInt ("NumOwners" )); - game.setNum_perfects (results.getInt ("NumPerfects" )); - games.add(game); - } - - return games; - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/backend/src/main/java/achievements/services/UserService.java b/backend/src/main/java/achievements/services/UserService.java deleted file mode 100644 index 56aa863..0000000 --- a/backend/src/main/java/achievements/services/UserService.java +++ /dev/null @@ -1,256 +0,0 @@ -package achievements.services; - -import achievements.data.Profile; -import achievements.data.request.AddPlatform; -import achievements.data.request.RemovePlatform; -import achievements.data.request.SetUsername; -import achievements.data.response.search.Achievement; -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.List; - -import static achievements.services.ImageService.MIME_TO_EXT; - -@Service -public class UserService { - - @Autowired - private DbConnection dbs; - private Connection db; - - @Autowired - private AuthenticationService auth; - - @Autowired - private APIService apiService; - - @Autowired - private ImageService imageService; - - @PostConstruct - private void init() { - db = dbs.getConnection(); - } - - public Profile getProfile(int userId) { - try { - var profile = (Profile) null; - { - var stmt = db.prepareCall("{? = call GetUserNameAndStats(?, ?, ?, ?, ?)}"); - stmt.registerOutParameter(1, Types.INTEGER); - stmt.setInt(2, userId); - stmt.registerOutParameter(3, Types.VARCHAR); - stmt.registerOutParameter(4, Types.INTEGER); - stmt.registerOutParameter(5, Types.INTEGER); - stmt.registerOutParameter(6, Types.INTEGER); - - stmt.execute(); - if (stmt.getInt(1) == 0) { - profile = new Profile(); - profile.setUsername(stmt.getString(3)); - profile.setCompleted(stmt.getInt(4)); - var average = stmt.getString(5); - profile.setPerfect(stmt.getInt(6)); - - if (average != null) { - profile.setAverage(Integer.parseInt(average)); - } - } else { - return null; - } - } - - { - var stmt = db.prepareCall("{call GetUserPlatforms(?)}"); - stmt.setInt(1, userId); - - var results = stmt.executeQuery(); - var platforms = new ArrayList(); - while (results.next()) { - var platform = new Profile.Platform(); - platform.setId (results.getInt ("ID" )); - platform.setName (results.getString ("PlatformName")); - platform.setConnected(results.getBoolean("Connected" )); - platforms.add(platform); - } - profile.setPlatforms(platforms); - } - - { - var stmt = db.prepareCall("{call GetRatingsByUser(?)}"); - stmt.setInt(1, userId); - - var results = stmt.executeQuery(); - var ratings = new ArrayList(); - while (results.next()) { - var rating = new Profile.Rating(); - rating.setAchievementId(results.getInt("AchievementID")); - rating.setName(results.getString("Name")); - rating.setDifficulty(results.getFloat("Difficulty")); if (results.wasNull()) { rating.setDifficulty(null); } - rating.setQuality(results.getFloat("Quality")); if (results.wasNull()) { rating.setQuality(null); } - rating.setReview(results.getString("Description")); - ratings.add(rating); - } - profile.setRatings(ratings); - } - - return profile; - } catch (SQLException e) { - e.printStackTrace(); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - return null; - } - - public int setUsername(int userId, SetUsername username) { - try { - if (auth.session().validate(userId, username.getSessionKey()) && username.getUsername().length() > 0 && username.getUsername().length() <= 32) { - var stmt = db.prepareCall("{call SetUsername(?, ?)}"); - stmt.setInt(1, userId); - stmt.setString(2, username.getUsername()); - - stmt.execute(); - return 0; - } - } catch (Exception e) { - e.printStackTrace(); - } - return -1; - } - - public String[] getProfileImage(int userId) { - try { - var stmt = db.prepareCall("{call GetUserImage(?)}"); - return imageService.getImageType(stmt, userId); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public String setProfileImage(int userId, String sessionKey, MultipartFile file) { - try { - var type = file.getContentType(); - if (type.matches("image/.*")) { - type = type.substring(6); - type = MIME_TO_EXT.get(type); - if (!auth.session().validate(userId, sessionKey)) { - return "forbidden"; - } else if (type == null) { - return "unsupported_type"; - } else { - 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); - - // 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"; - } - } catch (Exception e) { - e.printStackTrace(); - } - return "unknown"; - } - - public int addPlatform(int userId, AddPlatform request, boolean validate) { - if (!validate || 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(); - - 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(); - } - } - return -1; - } - - public int removePlatform(int userId, RemovePlatform request) { - try { - if (auth.session().validate(userId, request.getSessionKey())) { - var stmt = db.prepareCall("{call RemoveUserFromPlatform(?, ?)}"); - stmt.setInt(1, userId); - stmt.setInt(2, request.getPlatformId()); - - stmt.execute(); - - return 0; - } - } catch (Exception e) { - e.printStackTrace(); - } - return -1; - } - - public List getNoteworthy(int userId) { - try { - var stmt = db.prepareCall("{call GetNoteworthyAchievementsForUser(?)}"); - stmt.setInt(1, userId); - - var results = stmt.executeQuery(); - var achievements = new ArrayList(); - while (results.next()) { - var achievement = new Achievement(); - achievement.setID(results.getInt("ID")); - achievement.setName(results.getString("Name")); - achievement.setCompletion(results.getInt("Completion")); - achievements.add(achievement); - } - return achievements; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 16fd9b6..589c0f1 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1,2 @@ server.port = 4730 spring.application.name = Achievements Project -spring.jackson.default-property-inclusion=always - -server.session.cookie.secure = false - -spring.servlet.multipart.max-file-size = 10MB -spring.servlet.multipart.max-request-size = 10MB \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index a9e3966..8967c66 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,6 +1,3 @@ # Node files node_modules/ package-lock.json - -# Import Data -import.json diff --git a/frontend/config/base.json b/frontend/config/base.json index 1260d47..0967ef4 100644 --- a/frontend/config/base.json +++ b/frontend/config/base.json @@ -1,5 +1 @@ -{ - "hosts": { - "backend": "https://localhost:4730" - } -} +{} diff --git a/frontend/config/debug.json b/frontend/config/debug.json index b5c0161..912143b 100644 --- a/frontend/config/debug.json +++ b/frontend/config/debug.json @@ -2,9 +2,6 @@ "extends": [ "config/base.json" ], - "hosts": { - "frontend": "http://localhost:8080" - }, "build": "debug", "port": 8080 } diff --git a/frontend/config/release.json b/frontend/config/release.json index 1f6800c..d8c144f 100644 --- a/frontend/config/release.json +++ b/frontend/config/release.json @@ -2,9 +2,6 @@ "extends": [ "config/base.json" ], - "hosts": { - "frontend": "http://localhost" - }, "build": "release", "port": 80 } diff --git a/frontend/package.json b/frontend/package.json index a026d25..155262f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,19 +3,17 @@ "version": "1.0.0", "description": "Cross platform achievement tracker", "repository": "github:Gnarwhal/AchievementProject", - "main": "server.js", + "main": "static_server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "debug": "node server.js config/debug.json", - "release": "node server.js config/release.json" + "debug": "node static_server.js config/debug.json", + "release": "node static_server.js config/release.json" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "morgan": "^1.10.0", - "passport": "^0.4.1", - "passport-steam": "^1.0.15", - "promptly": "^3.2.0" + "xml2js": "^0.4.23" } } diff --git a/frontend/server.js b/frontend/server.js deleted file mode 100644 index ac88714..0000000 --- a/frontend/server.js +++ /dev/null @@ -1,54 +0,0 @@ -const fs = require('fs' ); -const path = require('path' ); - -const https = require('https' ); -const express = require('express' ); -const morgan = require('morgan' ); -const passport = require('passport'); -const SteamStrategy = require('passport-steam').Strategy; - -const promptly = require('promptly'); - -const config = require('./config.js').load(process.argv[2]); - -console.log(`Running server at '${config.hosts.frontend}'`); - -passport.use(new SteamStrategy({ - returnURL: `${config.hosts.frontend}/user/steam`, - realm: `${config.hosts.frontend}`, - profile: false, -})); - -const app = express(); -app.use("/", morgan("dev")); -app.use("/static", express.static("webpage/static")); -app.get("/login", (req, res) => res.sendFile(path.join(__dirname + "/webpage/login.html"))); -app.get("/", (req, res) => res.sendFile(path.join(__dirname + "/webpage/search_achievements.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("/import", (req, res) => res.sendFile(path.join(__dirname + "/webpage/import.html"))); -app.get("/achievement/:id", (req, res) => res.sendFile(path.join(__dirname + "/webpage/achievement.html"))); -app.get("/user/:id", (req, res) => res.sendFile(path.join(__dirname + "/webpage/user.html"))); -app.get("/auth/steam", passport.authenticate('steam'), (req, res) => {}); - -// --- API Forward --- // - -app.use("/api/*", (req, res) => { - res.redirect(307, `${config.hosts.backend}/${req.params[0]}`) -}); - -// ------------------- // - -const server = app.listen(config.port); - -const prompt = input => { - if (/q(?:uit)?|exit/i.test(input)) { - server.close(); - } else { - promptly.prompt('') - .then(prompt); - } -}; - -prompt(); \ No newline at end of file diff --git a/frontend/static_server.js b/frontend/static_server.js new file mode 100644 index 0000000..75509c5 --- /dev/null +++ b/frontend/static_server.js @@ -0,0 +1,16 @@ +const express = require('express'); +const morgan = require('morgan' ); +const fs = require('fs' ); +const https = require('https' ); + +const config = require('./config.js').load(process.argv[2]); + +if (config.build === 'debug') { + process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; +} + +const app = express(); +app.use("/", morgan("dev")); +app.use("/", express.static("webpage")); + +app.listen(config.port); \ No newline at end of file diff --git a/frontend/webpage/achievement.html b/frontend/webpage/achievement.html deleted file mode 100644 index b33132a..0000000 --- a/frontend/webpage/achievement.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - Achievements Project - - - - - - - -
-
-
- -
-
-

Contemplating...

- Loading Symbol -
- -
-
- - - - - \ No newline at end of file diff --git a/frontend/webpage/import.html b/frontend/webpage/import.html deleted file mode 100644 index 74e9cfb..0000000 --- a/frontend/webpage/import.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Achievements Project | Import - - - - - - -
-
-
- -
-
-
-
-
- Import Icon - Import Icon Hover -
-
-
-
-
-
-
-
- - - - - - \ No newline at end of file diff --git a/frontend/webpage/index.html b/frontend/webpage/index.html new file mode 100644 index 0000000..f5ccc0d --- /dev/null +++ b/frontend/webpage/index.html @@ -0,0 +1,35 @@ + + + + + Achievements Project + + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/frontend/webpage/login.html b/frontend/webpage/login.html deleted file mode 100644 index 1614fa9..0000000 --- a/frontend/webpage/login.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Achievements Project | Login - - - - - - -
-
-
- -
-
-
-
-
-

Login

-
-
-
-

Egg

- - - - -
- - -
- -

WARNING!

-

The security of this project is questionable at best. Please refrain from using any truly sensitive data.

-
-
-
-
-
-
- - - - - - \ No newline at end of file diff --git a/frontend/webpage/res/dummy_achievement.png b/frontend/webpage/res/dummy_achievement.png new file mode 100644 index 0000000..3441a23 Binary files /dev/null and b/frontend/webpage/res/dummy_achievement.png differ diff --git a/frontend/webpage/res/dummy_game.png b/frontend/webpage/res/dummy_game.png new file mode 100644 index 0000000..e98587b Binary files /dev/null and b/frontend/webpage/res/dummy_game.png differ diff --git a/frontend/webpage/res/psn.png b/frontend/webpage/res/psn.png new file mode 100644 index 0000000..cc2a8b2 Binary files /dev/null and b/frontend/webpage/res/psn.png differ diff --git a/frontend/webpage/res/steam.png b/frontend/webpage/res/steam.png new file mode 100644 index 0000000..3098061 Binary files /dev/null and b/frontend/webpage/res/steam.png differ diff --git a/frontend/webpage/res/temp_pfp.png b/frontend/webpage/res/temp_pfp.png new file mode 100644 index 0000000..a33bede Binary files /dev/null and b/frontend/webpage/res/temp_pfp.png differ diff --git a/frontend/webpage/res/xbox.png b/frontend/webpage/res/xbox.png new file mode 100644 index 0000000..6f2730c Binary files /dev/null and b/frontend/webpage/res/xbox.png differ diff --git a/frontend/webpage/scripts/index.js b/frontend/webpage/scripts/index.js new file mode 100644 index 0000000..a9a1460 --- /dev/null +++ b/frontend/webpage/scripts/index.js @@ -0,0 +1,95 @@ +const expandTemplates = async () => { + template.apply("navbar").values([ + { section: "left" }, + { section: "right" } + ]); + template.apply("navbar-section-left").values([ + { item: "games", title: "Games" }, + { item: "achievements", title: "Achievements" } + ]); + template.apply("navbar-section-right").values([ + { item: "profile", title: "Profile" } + ]); + template.apply("content-body").values([ + { page: "games", title: "Games" }, + { page: "achievements", title: "Achievements" }, + { page: "profile", title: "Profile" } + ]); + template.apply("extern-games-page" ).values("games_page" ); + template.apply("extern-achievements-page").values("achievements_page"); + template.apply("extern-profile-page" ).values("profile_page" ); + template.apply("achievements-page-list" ).fetch("achievements", "https://localhost:4730/achievements"); + + await template.expand(); +}; + +let pages = null; +const loadPages = () => { + pages = document.querySelectorAll(".page"); +} + +const connectNavbar = () => { + const navItems = document.querySelectorAll(".navbar-item"); + + for (const item of navItems) { + item.addEventListener("click", (clickEvent) => { + const navItemPageId = item.dataset.pageName + "-page" + for (const page of pages) { + if (page.id === navItemPageId) { + page.style.display = "block"; + } else { + page.style.display = "none"; + } + } + }); + } +}; + +const connectProfile = () => { + const games = document.querySelector("#profile-games"); + const achievements = document.querySelector("#profile-achievements"); + + games.children[0].addEventListener("click", (clickEvent) => { + for (const page of pages) { + if (page.id === "games-page") { + page.style.display = "block"; + } else { + page.style.display = "none"; + } + } + }); + + achievements.children[0].addEventListener("click", (clickEvent) => { + for (page of pages) { + if (page.id === "achievements-page") { + page.style.display = "block"; + } else { + page.style.display = "none"; + } + } + }); +} + +const loadFilters = () => { + const filters = document.querySelectorAll(".list-page-filter"); + for (let filter of filters) { + filter.addEventListener("click", (clickEvent) => { + if (filter.classList.contains("selected")) { + filter.classList.remove("selected"); + } else { + filter.classList.add("selected"); + } + }); + } +} + +window.addEventListener("load", async (loadEvent) => { + await expandTemplates(); + + loadPages(); + + connectNavbar(); + connectProfile(); + + loadFilters(); +}); diff --git a/frontend/webpage/scripts/template.js b/frontend/webpage/scripts/template.js new file mode 100644 index 0000000..7e302ea --- /dev/null +++ b/frontend/webpage/scripts/template.js @@ -0,0 +1,198 @@ +var template = template || {}; + +template.type = {}; +template.type._entryMap = new Map(); +template.type.register = (type, callback) => { + if (typeof type !== 'string') { + console.error(`'type' must be a string, recieved: `, type); + } else { + const TYPE_REGEX = /^(\w+)\s*(<\s*\?(?:\s*,\s*\?)*\s*>)?\s*$/; + const result = type.match(TYPE_REGEX); + if (result === null) { + console.error(`'${type}' is not a valid type id`); + } else { + if (result[2] === undefined) { + result[2] = 0; + } else { + result[2] = result[2].split(/\s*,\s*/).length; + } + const completeType = result[1] + ':' + result[2]; + if (template.type._entryMap.get(completeType) === undefined) { + template.type._entryMap.set(completeType, async function() { + await callback.apply(null, Array.from(arguments)); + }); + } else { + console.error(`${type} is already a registered template!`); + } + } + } +}; + +// Courtesy of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/* Intrinsic Templates */ + +// Basic - Simple search and replace +template.type.register('Basic', (element, map) => { + let html = element.innerHTML; + function applyObject(object, path) { + for (const key in object) { + const regexKey = escapeRegExp(path + key); + html = html.replace(new RegExp(`(?:(? { + return fetch(`templates/${name}.html.template`, { + method: 'GET', + mode: 'no-cors', + headers: { + 'Content-Type': 'text/plain' + } + }).then(response => response.text().then((data) => { + element.outerHTML = data; + })).catch(error => { + console.error(`failed to retrieve template '${name}': `, error); + }); +}); + +// List - Iterate over list and emit copy of child for each iteration +template.type.register('List', async (element, subtype, arrayMap) => { + let cumulative = ''; + const temp = document.createElement('template'); + for (const obj of arrayMap) { + temp.innerHTML = ``; + const child = temp.content.children[0]; + child.innerHTML = element.innerHTML; + const callback = template.type._entryMap.get(subtype.type); + if (callback === undefined) { + cumulative = ''; + console.error(`'${subtype.type}' is not a registered template type`); + } else { + await callback.apply(null, [ child, obj ]); + } + cumulative = cumulative + temp.innerHTML.trim(); + } + element.outerHTML = cumulative; +}); + +template._entryMap = new Map(); +template.apply = function(pattern, promise) { + if (typeof pattern !== 'string') { + console.error('pattern must be a string, received: ', pattern); + } else { + return new template.apply.applicators(pattern); + } +}; +template.apply.applicators = class { + constructor(pattern) { + this._pattern = pattern; + } + + _apply(asyncArgs) { + template._entryMap.set(RegExp('^' + this._pattern + '$'), asyncArgs); + } + + values(...args) { + this._apply(async () => Array.from(args)); + } + + promise(promise) { + let args = null; + promise = promise.then(data => args = [ data ]); + this._apply(async () => args || promise); + } + + fetch(dataProcessor, url, options) { + if (typeof dataProcessor === 'string') { + const path = dataProcessor; + dataProcessor = data => { + for (const id of path.split(/\./)) { + data = data[id]; + if (data === undefined) { + throw `invalid path '${path}'`; + } + } + + return data; + }; + }; + this.promise( + fetch(url, options || { method: 'GET', mode: 'cors' }) + .then(response => response.json()) + .then(data => dataProcessor(data)) + ); + } +}; + +(() => { + const parseType = (type) => { + let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/); + let id = result[1]; + result = result[2] ? result[2].split(/\s*,\s*/).map(parseType) : []; + return { type: id + ':' + result.length, params: result }; + }; + + const findTemplates = (element) => + Array + .from(element.querySelectorAll('template')) + .filter(child => Boolean(child.dataset.template)) + .map(child => { + const data = child.dataset.template.split(/\s*:\s*/); + return { + id: data[0], + typeCapture: parseType(data[1] || 'Begin'), + element: child + }; + }); + + const expand = async (element) => { + let children = findTemplates(element); + let promises = []; + let parents = new Set(); + for (const child of children) { + for (const [pattern, argsCallback] of template._entryMap) { + await argsCallback().then(args => { + if (pattern.test(child.id)) { + const callback = template.type._entryMap.get(child.typeCapture.type); + if (typeof callback !== 'function') { + console.error(`'${child.typeCapture.type}' is not a registered template type`); + } else { + let params = Array.from(args) + for (const subtype of child.typeCapture.params) { + params.unshift(subtype); + } + params.unshift(child.element); + let parent = child.element.parentElement; + if (!parents.has(parent)) { + parents.add(parent); + } + promises.push(callback.apply(null, params)); + } + } + }).catch(error => { + console.error('failed to retrieve arguments: ', error); + }); + } + } + await Promise.all(promises); + promises = []; + for (const parent of parents) { + promises.push(expand(parent)); + } + await Promise.all(promises); + }; + + template.expand = async () => expand(document.children[0]); +})(); \ No newline at end of file diff --git a/frontend/webpage/search_achievements.html b/frontend/webpage/search_achievements.html deleted file mode 100644 index cd93d68..0000000 --- a/frontend/webpage/search_achievements.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Achievements Project - - - - - - - - -
- -
- - - - - - \ No newline at end of file diff --git a/frontend/webpage/search_games.html b/frontend/webpage/search_games.html deleted file mode 100644 index 2c5a634..0000000 --- a/frontend/webpage/search_games.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Achievements Project - - - - - - - - -
- -
- - - - - - \ No newline at end of file diff --git a/frontend/webpage/search_users.html b/frontend/webpage/search_users.html deleted file mode 100644 index 59dd860..0000000 --- a/frontend/webpage/search_users.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Achievements Project - - - - - - - - -
- -
- - - - - - \ No newline at end of file diff --git a/frontend/webpage/static/res/cancel-hover.svg b/frontend/webpage/static/res/cancel-hover.svg deleted file mode 100644 index bae7ec4..0000000 --- a/frontend/webpage/static/res/cancel-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/cancel.svg b/frontend/webpage/static/res/cancel.svg deleted file mode 100644 index f344111..0000000 --- a/frontend/webpage/static/res/cancel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/completion.svg b/frontend/webpage/static/res/completion.svg deleted file mode 100644 index 218b4ba..0000000 --- a/frontend/webpage/static/res/completion.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/dropdown-hover.svg b/frontend/webpage/static/res/dropdown-hover.svg deleted file mode 100644 index 2382e2b..0000000 --- a/frontend/webpage/static/res/dropdown-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/dropdown.svg b/frontend/webpage/static/res/dropdown.svg deleted file mode 100644 index fed35a7..0000000 --- a/frontend/webpage/static/res/dropdown.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/edit-hover.svg b/frontend/webpage/static/res/edit-hover.svg deleted file mode 100644 index 63ab387..0000000 --- a/frontend/webpage/static/res/edit-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/edit.svg b/frontend/webpage/static/res/edit.svg deleted file mode 100644 index 8c1299c..0000000 --- a/frontend/webpage/static/res/edit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/import-hover.svg b/frontend/webpage/static/res/import-hover.svg deleted file mode 100644 index 3333fcd..0000000 --- a/frontend/webpage/static/res/import-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/import.svg b/frontend/webpage/static/res/import.svg deleted file mode 100644 index 580503e..0000000 --- a/frontend/webpage/static/res/import.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/loading.svg b/frontend/webpage/static/res/loading.svg deleted file mode 100644 index 8debb61..0000000 --- a/frontend/webpage/static/res/loading.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/save-hover.svg b/frontend/webpage/static/res/save-hover.svg deleted file mode 100644 index 0f23a7c..0000000 --- a/frontend/webpage/static/res/save-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/save.svg b/frontend/webpage/static/res/save.svg deleted file mode 100644 index be0a911..0000000 --- a/frontend/webpage/static/res/save.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/upload-hover.svg b/frontend/webpage/static/res/upload-hover.svg deleted file mode 100644 index 6a91f83..0000000 --- a/frontend/webpage/static/res/upload-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/upload-invalid.svg b/frontend/webpage/static/res/upload-invalid.svg deleted file mode 100644 index 2fe8002..0000000 --- a/frontend/webpage/static/res/upload-invalid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/res/upload.svg b/frontend/webpage/static/res/upload.svg deleted file mode 100644 index cf41f16..0000000 --- a/frontend/webpage/static/res/upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/webpage/static/scripts/achievement.js b/frontend/webpage/static/scripts/achievement.js deleted file mode 100644 index 94367c3..0000000 --- a/frontend/webpage/static/scripts/achievement.js +++ /dev/null @@ -1,167 +0,0 @@ -let achievementId = window.location.pathname.split('/').pop(); -let isReturn = false; -let achievementData = null; -let myRating = {}; -const loadAchievement = () => { - if (myRating.invalid) { - document.querySelector("#achievement-rating").remove(); - } - - const description = document.querySelector("#achievement-description-text"); - if (description.textContent === '') { - description.remove(); - } - - // Canvasing - - const completionCanvas = document.querySelector("#achievement-completion-canvas"); - - const STROKE_WIDTH = 0.18; - const style = window.getComputedStyle(completionCanvas); - const context = completionCanvas.getContext('2d'); - - const drawCanvas = () => achievementData.then(data => { - const width = Number(style.getPropertyValue('width').slice(0, -2)); - const height = width; - - context.canvas.width = width; - context.canvas.height = height; - context.clearRect(0, 0, width, height); - context.strokeStyle = root.getProperty('--accent-value3'); - context.lineWidth = (width / 2) * STROKE_WIDTH; - context.beginPath(); - context.arc(width / 2, height / 2, (width / 2) * (1 - STROKE_WIDTH / 2), -0.5 * Math.PI, (-0.5 + (data.completion === null ? 0 : (data.completion / 100) * 2)) * Math.PI); - context.stroke(); - }); - - window.addEventListener('resize', drawCanvas); - drawCanvas(); - - if (!myRating.invalid) { - const saveReview = document.querySelector("#rating-save-stack"); - - const myDifficulty = document.querySelector("#achievement-difficulty-rating-text"); - const myQuality = document.querySelector("#achievement-quality-rating-text"); - const myReview = document.querySelector("#achievement-review-rating-text"); - - const reviewInput = () => { - saveReview.style.display = 'block'; - } - myDifficulty.addEventListener('input', reviewInput); - myQuality.addEventListener('input', reviewInput); - myReview.addEventListener('input', reviewInput); - - const saveInputOnEnter = (keyEvent) => { - if (keyEvent.key === 'Enter') { - saveReview.click(); - } - } - myDifficulty.addEventListener('keydown', saveInputOnEnter); - myQuality.addEventListener('keydown', saveInputOnEnter); - - saveReview.addEventListener('click', (clickEvent) => { - let successful = true; - const difficulty = Number(myDifficulty.value); - const quality = Number(myQuality.value ); - if ((Number.isNaN(difficulty) && myDifficulty.value !== '') || difficulty < 0 || difficulty > 10) { - myDifficulty.style.backgroundColor = 'var(--error)'; - successful = false; - } - if ((Number.isNaN(quality) && myQuality.value !== '') || quality < 0 || quality > 10) { - myQuality.style.backgroundColor = 'var(--error)'; - successful = false; - } - if (successful) { - myDifficulty.style.backgroundColor = 'var(--foreground)'; - myQuality.style.backgroundColor = 'var(--foreground)'; - saveReview.style.display = 'none'; - fetch(`/api/achievement/${achievementId}/rating/${session.id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - sessionKey: session.key, - difficulty: difficulty, - quality: quality, - review: myReview.value - }) - }) - .then(response => { - if (response.status === 401) { - responese.json().then(data => { - myDifficulty.value = data.difficulty ? data.difficulty : ''; - myQuality.value = data.quality ? data.quality : ''; - myReview.value = data.review ? data.review : ''; - }); - } - }); - } - }); - } - - { - const ratings = document.querySelectorAll(".list-page-entry.rating"); - for (const rating of ratings) { - rating.addEventListener("click", (clickEvent) => { - window.location.href = `/user/${rating.dataset.id}`; - }); - } - } -} - -const expandTemplates = async () => { - await commonTemplates(); - if (session.key) { - myRating = await fetch(`/api/achievement/${achievementId}/rating/${session.id}`, { method: 'GET' }) - .then(response => { - if (response.status !== 200) { - return { invalid: true }; - } else { - return response.json(); - } - }); - } else { - myRating = { invalid: true }; - } - template.apply("achievement-page").promise(achievementData.then(data => ({ - id: achievementId, - name: data.name, - description: data.description ? data.description : '', - completion: data.completion === null ? "N/A" : `${data.completion}%`, - difficulty: data.difficulty === null ? "N/A" : `${data.difficulty} / 10`, - quality: data.quality === null ? "N/A" : `${data.quality} / 10`, - my_difficulty: myRating.difficulty ? myRating.difficulty : '', - my_quality: myRating.quality ? myRating.quality : '', - my_review: myRating.review ? myRating.review : '', - }))); - template.apply("rating-list").promise(achievementData.then(data => data.ratings.map(data => ({ - user_id: data.userId, - user_username: data.username, - user_difficulty: data.difficulty, - user_quality: data.quality, - user_review: data.review - })))); -} - -window.addEventListener("load", async (loadEvent) => { - await loadCommon(); - - var importing = document.querySelector("#importing"); - if (/\d+/.test(achievementId)) { - achievementId = Number(achievementId); - } else { - // Handle error - } - importing.remove(); - - achievementData = fetch(`/api/achievement/${achievementId}`, { method: 'GET' }) - .then(response => response.json()); - - await expandTemplates(); - await template.expand(); - - loadLazyImages(); - connectNavbar(); - loadAchievement(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/common.js b/frontend/webpage/static/scripts/common.js deleted file mode 100644 index 190059e..0000000 --- a/frontend/webpage/static/scripts/common.js +++ /dev/null @@ -1,120 +0,0 @@ -let root = null; -const loadRoot = () => { - const rootElement = document.documentElement; - - root = {}; - root.getProperty = (name) => window.getComputedStyle(document.documentElement).getPropertyValue(name); - root.setProperty = (name, value) => { - rootElement.style.setProperty(name, value); - } -}; - -let session = { id: null }; -const clearSession = () => session = { id: null }; -const loadSession = async () => { - window.addEventListener('beforeunload', (beforeUnloadEvent) => { - window.sessionStorage.setItem('session', JSON.stringify(session)); - }); - - session = JSON.parse(window.sessionStorage.getItem('session')) || { id: -1 }; - if (session.hue) { - root.setProperty('--accent-hue', session.hue); - } - - if (session.id !== null) { - await fetch(`/api/auth/refresh`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ key: session.key, id: session.id }) - }) - .then(async response => ({ status: response.status, data: await response.json() })) - .then(response => { - if (response.status !== 200 && window.location.pathname !== "/login") { - session.id = null; - session.key = null; - if (session.id !== -1) { - window.location.href = "/login"; - } - } else { - session.key = response.data.key; - session.id = response.data.id; - if (session.id === -1 && window.location.pathname !== '/import') { - window.location.href = '/import'; - } - } - }); - } -}; - -const authenticate = (obj) => { - obj.sessionKey = session.key; - obj.userId = session.id; - return obj; -} - -const loadCommon = async () => { - loadRoot(); - await loadSession(); -} - -const commonTemplates = async () => { - template.apply("navbar").values([ - { section: "left" }, - { section: "right" } - ]); - template.apply("navbar-section-left").values([ - { item: "achievements", title: "Achievements" }, - { item: "users", title: "Users" }, - { item: "games", title: "Games" }, - { item: "import", title: "Import" } - ]); - if (session.id !== -1 && session.id !== null) { - template.apply("navbar-section-right").values([ - { item: "profile", title: "Profile" }, - { item: "logout", title: "Logout" } - ]); - } else { - template.apply("navbar-section-right").values([ - { item: "login", title: "Login / Create Account" } - ]); - } -}; - -const loadLazyImages = () => { - const imgs = document.querySelectorAll(".lazy-img"); - for (const img of imgs) { - img.src = img.dataset.src; - } -} - -const connectNavbar = () => { - if (session.id !== -1) { - const navItems = document.querySelectorAll(".navbar-item"); - - if (!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', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ key: session.key }) - }); - clearSession(); - window.location.href = "/login"; - }); - } else if (item.dataset.pageName === "profile") { - item.addEventListener("click", (clickEvent) => window.location.href = `/user/${session.id}`); - } else { - item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`); - } - } - } -}; \ No newline at end of file diff --git a/frontend/webpage/static/scripts/import.js b/frontend/webpage/static/scripts/import.js deleted file mode 100644 index 05c0eaf..0000000 --- a/frontend/webpage/static/scripts/import.js +++ /dev/null @@ -1,105 +0,0 @@ -let consoleTop = true; -let importConsole = null; -const appendLine = (line) => { - const template = document.createElement("template"); - template.innerHTML = `

${line}

` - importConsole.appendChild(template.content.firstElementChild); - consoleTop = false; -}; - -const loadConsole = () => { - importConsole = document.querySelector("#import-console"); - - const dropzone = document.querySelector("#import-dropzone"); - const uploadWrapper = document.querySelector("#upload-wrapper"); - const upload = (dropEvent) => { - dropEvent.preventDefault(); - - dropzone.classList.remove('active'); - if (dropEvent.dataTransfer.files) { - const file = dropEvent.dataTransfer.files[0]; - if (file.type === 'application/json') { - importConsole.style.display = 'block'; - uploadWrapper.style.display = 'none'; - file.text().then(data => JSON.parse(data)).then(data => { - let uploads = Promise.resolve(); - for (let i = 0; i < data.platforms.length; ++i) { - const platform = data.platforms[i]; - uploads = uploads - .then(() => { - appendLine(`(${i + 1}/${data.platforms.length}) Creating platform: ${platform.name}`); - }).then(() => fetch( - '/api/import/platform', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(authenticate(platform)) - } - ) - ); - } - for (let i = 0; i < data.users.length; ++i) { - const user = data.users[i]; - const userPlatforms = user.platforms; - delete user.platforms; - uploads = uploads - .then(() => { - appendLine(`(${i + 1}/${data.users.length}) Creating user: ${user.username}`); - }).then(() => fetch( - '/api/import/user', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(authenticate(user)) - } - ) - ); - for (let j = 0; j < userPlatforms.length; ++j) { - const platform = userPlatforms[j]; - platform.userEmail = user.email; - uploads = uploads - .then(() => { - appendLine(`    (${j + 1}/${userPlatforms.length}) Importing platform data: ${data.platforms[platform.platformId].name}`); - }).then(() => fetch( - '/api/import/user/platform', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(authenticate(platform)) - } - ) - ); - } - } - uploads = uploads.then(() => { - if (session.id === -1) { - clearSession(); - window.location.href = '/login'; - } else { - importConsole.innerHTML = ''; - importConsole.style.display = 'none'; - uploadWrapper.style.display = 'block'; - } - }); - }); - } - } - }; - dropzone.addEventListener("drop", upload); - dropzone.addEventListener("dragover", (dragEvent) => { - dragEvent.preventDefault(); - }); -}; - -window.addEventListener("load", async (loadEvent) => { - await loadCommon(); - - await commonTemplates(); - await template.expand(); - - connectNavbar(); - loadConsole(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/login.js b/frontend/webpage/static/scripts/login.js deleted file mode 100644 index 4752721..0000000 --- a/frontend/webpage/static/scripts/login.js +++ /dev/null @@ -1,185 +0,0 @@ -window.addEventListener("load", async (loadEvent) => { - await loadCommon(); - - if (session.key) { - window.location.href = '/'; - } - - const fields = { - email: document.querySelector("#email" ), - username: document.querySelector("#username"), - password: document.querySelector("#password"), - confirm: document.querySelector("#confirm" ) - }; - fields.email.focus(); - - const createUser = document.querySelector("#create-user-button"); - const login = document.querySelector("#login-button"); - const guest = document.querySelector("#guest-login-button"); - - const header = document.querySelector("#login-header-text"); - const error = document.querySelector("#error-message"); - - if (!session.key && session.id) { - error.style.display = "block"; - error.textContent = "You have been signed out due to inactivity"; - } - - const raiseError = (errorFields, message) => { - for (const key in fields) { - if (errorFields.includes(key)) { - fields[key].classList.add("error"); - } else { - fields[key].classList.remove("error"); - } - } - - error.style.display = "block"; - error.textContent = message; - } - - let frozen = false; - const freeze = () => { - frozen = true; - createUser.classList.add("disabled"); - login.classList.add("disabled"); - guest.classList.add("disabled"); - }; - const unfreeze = () => { - frozen = false; - createUser.classList.remove("disabled"); - login.classList.remove("disabled"); - guest.classList.remove("disabled"); - }; - - const switchToCreateAction = (clickEvent) => { - if (!frozen) { - fields.username.style.display = "block"; - fields.confirm.style.display = "block"; - header.textContent = "Create Account"; - - createUser.removeEventListener("click", switchToCreateAction); - createUser.addEventListener("click", createUserAction); - - login.removeEventListener("click", loginAction); - login.addEventListener("click", switchToLoginAction); - - activeAction = createUserAction; - } - }; - const createUserAction = (clickEvent) => { - if (!frozen) { - if (fields.email.value === '') { - raiseError([ "email" ], "Email cannot be empty"); - } else if (fields.username.value === '') { - raiseError([ "username" ], "Username cannot be empty"); - } else if (fields.password.value !== fields.confirm.value) { - raiseError([ "password", "confirm" ], "Password fields did not match"); - } else if (fields.password.value === '') { - raiseError([ "password", "confirm" ], "Password cannot be empty"); - } else { - freeze(); - fetch(`/api/auth/create_user`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: fields.email.value, username: fields.username.value, password: fields.password.value }) - }) - .then(async response => ({ status: response.status, data: await response.json() })) - .then(response => { - const data = response.data; - if (response.status === 201) { - session = data; - window.location.href = `/user/${session.id}`; - } else if (response.status === 500) { - raiseError([], "Internal server error :("); - } else { - if (data.code === 1) { - raiseError([ "email" ], "A user with that email is already registered"); - fields.email.value = ''; - } else if (data.code === 2) { - raiseError([ "email" ], "Invalid email address"); - fields.email.value = ''; - } else { - raiseError([ "email" ], "Server is bad :p"); - fields.email.value = ''; - } - } - }) - .catch(error => { - console.error(error); - raiseError([], "Server error :("); - }).then(unfreeze); - } - } - }; - createUser.addEventListener("click", switchToCreateAction); - - const switchToLoginAction = (clickEvent) => { - if (!frozen) { - fields.username.style.display = "none"; - fields.confirm.style.display = "none"; - header.textContent = "Login"; - - createUser.removeEventListener("click", createUserAction); - createUser.addEventListener("click", switchToCreateAction); - - login.removeEventListener("click", switchToLoginAction); - login.addEventListener("click", loginAction); - - activeAction = loginAction; - } - }; - const loginAction = (clickEvent) => { - if (!frozen) { - if (fields.email.value === '') { - raiseError([ "email" ], "Email cannot be empty"); - } else if (fields.password.value === '') { - raiseError([ "password" ], "Password cannot be empty"); - } else { - freeze(); - fetch(`/api/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email: fields.email.value, password: fields.password.value }) - }) - .then(async response => ({ status: response.status, data: await response.json() })) - .then(response => { - const data = response.data; - if (response.status === 200) { - session = data; - window.location.href = "/"; - } else if (response.status === 500) { - raiseError([], "Internal server error :("); - } else { - raiseError([ "email", "password" ], "Email or password is incorrect"); - fields.password.value = ''; - } - }) - .catch(error => { - console.error(error); - raiseError([], "Unknown error :("); - }).then(unfreeze); - } - } - }; - login.addEventListener("click", loginAction); - - guest.addEventListener("click", (clickEvent) => { - if (!frozen) { - window.location.href = '/'; - } - }); - - let activeAction = loginAction; - for (const field in fields) { - fields[field].addEventListener("keydown", (keyEvent) => { - if (keyEvent.key === "Enter") { - activeAction(); - } - }) - } -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/search.js b/frontend/webpage/static/scripts/search.js deleted file mode 100644 index c2cac2a..0000000 --- a/frontend/webpage/static/scripts/search.js +++ /dev/null @@ -1,26 +0,0 @@ -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(); -}; \ No newline at end of file diff --git a/frontend/webpage/static/scripts/search_achievements.js b/frontend/webpage/static/scripts/search_achievements.js deleted file mode 100644 index 89bbb7e..0000000 --- a/frontend/webpage/static/scripts/search_achievements.js +++ /dev/null @@ -1,141 +0,0 @@ -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 personal = document.querySelector("#personal-filters"); - if (!session) { - personal.style.display = 'none'; - } - - 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 ordering = 'name'; - let direction = true; - let canSearch = true; - const loadList = async () => { - if (canSearch) { - canSearch = false; - - const body = { - searchTerm: searchField.value, - userId: completed.classList.contains('selected') ? session.id : null, - completed: completed.classList.contains('selected') ? true : null, - 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 ), - ordering: ordering, - orderDirection: direction ? 'ASC' : 'DESC', - }; - 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(); - - const entries = document.querySelectorAll(".list-page-entry.achievement"); - for (const entry of entries) { - entry.addEventListener("click", (clickEvent) => { - window.location.href = `/achievement/${entry.dataset.id}`; - }); - } - }); - - const headers = { - game: document.querySelector(".list-page-header > .achievement-game-name" ), - name: document.querySelector(".list-page-header > .achievement-name" ), - completion: document.querySelector(".list-page-header > .achievement-completion"), - difficulty: document.querySelector(".list-page-header > .achievement-difficulty"), - quality: document.querySelector(".list-page-header > .achievement-quality" ), - } - for (const header in headers) { - headers[header].addEventListener("click", (clickEvent) => { - if (ordering === header) { - direction = !direction; - } else { - ordering = header; - direction = true; - } - loadList(); - }); - } - } - }; - - 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(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/search_games.js b/frontend/webpage/static/scripts/search_games.js deleted file mode 100644 index 23c229b..0000000 --- a/frontend/webpage/static/scripts/search_games.js +++ /dev/null @@ -1,134 +0,0 @@ -let templateList = null; -let templateText = null; -const saveTemplate = () => { - const templateElement = document.querySelector("#game-list-template"); - templateList = templateElement.parentElement; - templateText = templateElement.outerHTML; - templateElement.remove(); -}; - -const loadGameSearch = () => { - const loading = document.querySelector("#loading-results"); - - const personal = document.querySelector("#personal-filters"); - if (!session) { - personal.style.display = 'none'; - } - - const searchButton = document.querySelector("#game-search-button"); - const searchField = document.querySelector("#game-search-field" ); - - const owned = document.querySelector("#owned-filter" ); - const minAvgCompletion = document.querySelector("#min-avg-completion-filter"); - const maxAvgCompletion = document.querySelector("#max-avg-completion-filter"); - const minNumOwners = document.querySelector("#min-num-owners-filter" ); - const maxNumOwners = document.querySelector("#max-num-owners-filter" ); - const minNumPerfects = document.querySelector("#min-num-perfects-filter" ); - const maxNumPerfects = document.querySelector("#max-num-perfects-filter" ); - - let ordering = 'name'; - let direction = true; - let canSearch = true; - const loadList = async () => { - if (canSearch) { - canSearch = false; - - const body = { - searchTerm: searchField.value, - userId: owned.classList.contains('selected') ? session.id : null, - owned: owned.classList.contains('selected') ? true : null, - minAvgCompletion: minAvgCompletion.value === '' ? null : Number(minAvgCompletion.value), - maxAvgCompletion: maxAvgCompletion.value === '' ? null : Number(maxAvgCompletion.value), - minNumOwners: minNumOwners.value === '' ? null : Number(minNumOwners.value ), - maxNumOwners: maxNumOwners.value === '' ? null : Number(maxNumOwners.value ), - minNumPerfects: minNumPerfects.value === '' ? null : Number(minNumPerfects.value ), - maxNumPerfects: maxNumPerfects.value === '' ? null : Number(maxNumPerfects.value ), - ordering: ordering, - orderDirection: direction ? 'ASC' : 'DESC', - }; - let successful = true; - if (Number.isNaN(body.minAvgCompletion)) { successful = false; minAvgCompletion.style.backgroundColor = 'var(--error)'; } else { minAvgCompletion.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxAvgCompletion)) { successful = false; maxAvgCompletion.style.backgroundColor = 'var(--error)'; } else { maxAvgCompletion.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.minNumOwners)) { successful = false; minNumOwners.style.backgroundColor = 'var(--error)'; } else { minNumOwners.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxNumOwners)) { successful = false; maxNumOwners.style.backgroundColor = 'var(--error)'; } else { maxNumOwners.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.minNumPerfects)) { successful = false; minNumPerfects.style.backgroundColor = 'var(--error)'; } else { minNumPerfects.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxNumPerfects)) { successful = false; maxNumPerfects.style.backgroundColor = 'var(--error)'; } else { maxNumPerfects.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/games", { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body) - }) - .then(response => response.json()) - - template.clear(); - template.apply('games-page-list').promise(data.then(data => data.map(item => ({ - game_id: item.ID, - game_name: item.name, - achievement_count: item.achievement_count, - avg_completion: item.avg_completion == null ? 'N/A' : `${item.avg_completion}%`, - num_owners: item.num_owners, - num_perfects: item.num_perfects, - })))); - await template.expand(); - data.then(data => { - loading.style.display = 'none'; - canSearch = true; - loadLazyImages(); - }); - - const headers = { - game: document.querySelector(".list-page-header > .game-name" ), - achievement_count: document.querySelector(".list-page-header > .game-achievement-count"), - avg_completion: document.querySelector(".list-page-header > .game-avg-completion" ), - num_owners: document.querySelector(".list-page-header > .game-num-owners" ), - num_perfects: document.querySelector(".list-page-header > .game-num-perfects" ), - } - for (const header in headers) { - headers[header].addEventListener("click", (clickEvent) => { - if (ordering === header) { - direction = !direction; - } else { - ordering = header; - direction = true; - } - loadList(); - }); - } - } - }; - - 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 loadGameSearch(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/search_users.js b/frontend/webpage/static/scripts/search_users.js deleted file mode 100644 index 4150e42..0000000 --- a/frontend/webpage/static/scripts/search_users.js +++ /dev/null @@ -1,132 +0,0 @@ -let templateList = null; -let templateText = null; -const saveTemplate = () => { - const templateElement = document.querySelector("#user-list-template"); - templateList = templateElement.parentElement; - templateText = templateElement.outerHTML; - templateElement.remove(); -}; - -const loadUserSearch = () => { - const loading = document.querySelector("#loading-results"); - - - const searchButton = document.querySelector("#user-search-button"); - const searchField = document.querySelector("#user-search-field" ); - - const minOwned = document.querySelector("#min-owned-filter" ); - const maxOwned = document.querySelector("#max-owned-filter" ); - const minCompleted = document.querySelector("#min-completed-filter" ); - const maxCompleted = document.querySelector("#max-completed-filter" ); - const minAvgCompletion = document.querySelector("#min-avg-completion-filter"); - const maxAvgCompletion = document.querySelector("#max-avg-completion-filter"); - - let ordering = 'name'; - let direction = true; - let canSearch = true; - const loadList = async () => { - if (canSearch) { - canSearch = false; - - const body = { - searchTerm: searchField.value, - minOwned: minOwned.value === '' ? null : Number(minOwned.value ), - maxOwned: maxOwned.value === '' ? null : Number(maxOwned.value ), - minCompleted: minCompleted.value === '' ? null : Number(minCompleted.value ), - maxCompleted: maxCompleted.value === '' ? null : Number(maxCompleted.value ), - minAvgCompletion: minAvgCompletion.value === '' ? null : Number(minAvgCompletion.value), - maxAvgCompletion: maxAvgCompletion.value === '' ? null : Number(maxAvgCompletion.value), - }; - let successful = true; - if (Number.isNaN(body.minOwned )) { successful = false; minOwned.style.backgroundColor = 'var(--error)'; } else { minOwned.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxOwned )) { successful = false; maxOwned.style.backgroundColor = 'var(--error)'; } else { maxOwned.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.minCompleted )) { successful = false; minCompleted.style.backgroundColor = 'var(--error)'; } else { minCompleted.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxCompleted )) { successful = false; maxCompleted.style.backgroundColor = 'var(--error)'; } else { maxCompleted.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.minAvgCompletion)) { successful = false; minAvgCompletion.style.backgroundColor = 'var(--error)'; } else { minAvgCompletion.style.backgroundColor = 'var(--foreground)'; } - if (Number.isNaN(body.maxAvgCompletion)) { successful = false; maxAvgCompletion.style.backgroundColor = 'var(--error)'; } else { maxAvgCompletion.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/users", { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body) - }) - .then(response => response.json()) - - template.clear(); - template.apply('user-page-list').promise(data.then(data => data.map(item => ({ - user_id: item.id, - username: item.username, - game_count: item.game_count, - achievement_count: item.achievement_count, - avg_completion: item.avg_completion == null ? 'N/A' : `${item.avg_completion}%`, - perfect_games: item.perfect_games - })))); - await template.expand(); - data.then(data => { - loading.style.display = 'none'; - canSearch = true; - loadLazyImages(); - - const entries = document.querySelectorAll(".list-page-entry.user"); - for (const entry of entries) { - entry.addEventListener("click", (clickEvent) => { - window.location.href = `/user/${entry.dataset.id}`; - }); - } - }); - - const headers = { - username: document.querySelector(".list-page-header > .user-username" ), - game_count: document.querySelector(".list-page-header > .user-game-count" ), - achievement_count: document.querySelector(".list-page-header > .user-achievement-count"), - avg_completion: document.querySelector(".list-page-header > .user-avg-completion" ), - perfect_games: document.querySelector(".list-page-header > .user-perfect-games" ), - } - for (const header in headers) { - headers[header].addEventListener("click", (clickEvent) => { - if (ordering === header) { - direction = !direction; - } else { - ordering = header; - direction = true; - } - loadList(); - }); - } - } - }; - - 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 loadUserSearch(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/template.js b/frontend/webpage/static/scripts/template.js deleted file mode 100644 index cb3541f..0000000 --- a/frontend/webpage/static/scripts/template.js +++ /dev/null @@ -1,201 +0,0 @@ -var template = template || {}; - -(() => { - templateTypeEntryMap = new Map(); - template.register = (type, callback) => { - if (typeof type !== 'string') { - console.error(`'type' must be a string, recieved: `, type); - } else { - const TYPE_REGEX = /^(\w+)\s*(<\s*\?(?:\s*,\s*\?)*\s*>)?\s*$/; - const result = type.match(TYPE_REGEX); - if (result === null) { - console.error(`'${type}' is not a valid type id`); - } else { - if (result[2] === undefined) { - result[2] = 0; - } else { - result[2] = result[2].split(/\s*,\s*/).length; - } - const completeType = result[1] + ':' + result[2]; - if (templateTypeEntryMap.get(completeType) === undefined) { - templateTypeEntryMap.set(completeType, async function() { - await callback.apply(null, Array.from(arguments)); - }); - } else { - console.error(`${type} is already a registered template!`); - } - } - } - }; - - // Courtesy of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); // $& means the whole matched string - } - - /* Intrinsic Templates */ - - // Basic - Simple search and replace - template.register('Basic', (element, map) => { - let html = element.innerHTML; - function applyObject(object, path) { - for (const key in object) { - const regexKey = escapeRegExp(path + key); - html = html.replace(new RegExp(`(?:(? { - return fetch(`templates/${name}.html.template`, { - method: 'GET', - mode: 'no-cors', - headers: { - 'Content-Type': 'text/plain' - } - }).then(response => response.text().then((data) => { - element.outerHTML = data; - })).catch(error => { - console.error(`failed to retrieve template '${name}': `, error); - }); - }); - - // List - Iterate over list and emit copy of child for each iteration - template.register('List', async (element, subtype, arrayMap) => { - let cumulative = ''; - const temp = document.createElement('template'); - for (const obj of arrayMap) { - temp.innerHTML = ``; - const child = temp.content.children[0]; - child.innerHTML = element.innerHTML; - const callback = templateTypeEntryMap.get(subtype.type); - if (callback === undefined) { - cumulative = ''; - console.error(`'${subtype.type}' is not a registered template`); - } else { - await callback.apply(null, [ child, obj ]); - } - cumulative = cumulative + temp.innerHTML.trim(); - } - element.outerHTML = cumulative; - }); - - templateEntryMap = new Map(); - template.apply = function(pattern, promise) { - if (typeof pattern !== 'string') { - console.error('pattern must be a string, received: ', pattern); - } else { - return new template.apply.applicators(pattern); - } - }; - template.apply.applicators = class { - constructor(pattern) { - this._pattern = pattern; - } - - _apply(asyncArgs) { - templateEntryMap.set(RegExp('^' + this._pattern + '$'), asyncArgs); - } - - values(...args) { - this._apply(async () => Array.from(args)); - } - - promise(promise) { - let args = null; - promise = promise.then(data => args = [ data ]); - this._apply(async () => args || promise); - } - - fetch(dataProcessor, url, options) { - if (typeof dataProcessor === 'string') { - const path = dataProcessor; - dataProcessor = data => { - for (const id of path.split(/\./)) { - data = data[id]; - if (data === undefined) { - throw `invalid path '${path}'`; - } - } - - return data; - }; - }; - this.promise( - fetch(url, options || { method: 'GET', mode: 'cors' }) - .then(response => response.json()) - .then(data => dataProcessor(data)) - ); - } - }; - - template.clear = () => { - templateEntryMap.clear(); - } - - const parseType = (type) => { - let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/); - let id = result[1]; - result = result[2] ? result[2].split(/\s*,\s*/).map(parseType) : []; - return { type: id + ':' + result.length, params: result }; - }; - - const findTemplates = (element) => - Array - .from(element.querySelectorAll('template')) - .filter(child => Boolean(child.dataset.template)) - .map(child => { - const data = child.dataset.template.split(/\s*:\s*/); - return { - id: data[0], - typeCapture: parseType(data[1] || 'Basic'), - element: child - }; - }); - - const expand = async (element) => { - let children = findTemplates(element); - let promises = []; - let parents = new Set(); - for (const child of children) { - for (const [pattern, argsCallback] of templateEntryMap) { - await argsCallback().then(args => { - if (pattern.test(child.id)) { - const callback = templateTypeEntryMap.get(child.typeCapture.type); - if (typeof callback !== 'function') { - console.error(`'${child.typeCapture.type}' is not a registered template`); - } else { - let params = Array.from(args) - for (const subtype of child.typeCapture.params) { - params.unshift(subtype); - } - params.unshift(child.element); - let parent = child.element.parentElement; - if (!parents.has(parent)) { - parents.add(parent); - } - promises.push(callback.apply(null, params)); - } - } - }).catch(error => { - console.error('failed to retrieve arguments: ', error); - }); - } - } - await Promise.all(promises); - promises = []; - for (const parent of parents) { - promises.push(expand(parent)); - } - await Promise.all(promises); - }; - - template.expand = async () => expand(document.children[0]); -})(); \ No newline at end of file diff --git a/frontend/webpage/static/scripts/user.js b/frontend/webpage/static/scripts/user.js deleted file mode 100644 index d62500d..0000000 --- a/frontend/webpage/static/scripts/user.js +++ /dev/null @@ -1,339 +0,0 @@ -let profileId = window.location.pathname.split('/').pop(); -let isReturn = false; -let profileData = null; -const loadProfile = () => { - const lists = document.querySelectorAll(".profile-list"); - const checkLists = () => { - for (const list of lists) { - 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) => { - return type === "image/apng" - || type === "image/avif" - || type === "image/gif" - || type === "image/jpeg" - || type === "image/png" - || type === "image/svg+xml" - || type === "image/webp" - } - - const editProfileButton = document.querySelector("#info-edit-stack"); - const saveProfileButton = document.querySelector("#info-save-stack"); - - const usernameText = document.querySelector("#profile-info-username-text"); - const usernameField = document.querySelector("#profile-info-username-field"); - - const finalizeName = () => { - if (usernameField.value !== '') { - fetch(`/api/user/${profileId}/username`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: `{ "sessionKey": "${session.key}", "username": "${usernameField.value}" }` - }).then(response => { - if (response.status === 201) { - usernameText.textContent = usernameField.value.substring(0, 32); - } - }); - } - }; - - usernameField.addEventListener("input", (inputEvent) => { - if (usernameField.value.length > 32) { - usernameField.value = usernameField.value.substring(0, 32); - } - }); - usernameField.addEventListener("keydown", (keyEvent) => { - if (keyEvent.key === "Enter") { - saveProfileButton.click(); - } - }) - - const pfp = document.querySelector("#profile-info-pfp-img"); - const pfpStack = document.querySelector("#profile-info-pfp"); - const upload = document.querySelector("#profile-info-pfp-upload"); - const uploadHover = document.querySelector("#profile-info-pfp-upload-hover"); - const uploadInvalid = document.querySelector("#profile-info-pfp-upload-invalid"); - - const togglePlatformEdit = (clickEvent) => { - editProfileButton.classList.toggle("active"); - saveProfileButton.classList.toggle("active"); - usernameText.classList.toggle("active"); - usernameField.classList.toggle("active"); - upload.classList.toggle("active"); - uploadHover.classList.toggle("active"); - uploadInvalid.classList.toggle("active"); - pfpStack.classList.remove("hover"); - pfpStack.classList.remove("invalid"); - }; - editProfileButton.addEventListener("click", togglePlatformEdit); - editProfileButton.addEventListener("click", () => { - usernameField.value = usernameText.textContent; - }); - saveProfileButton.addEventListener("click", togglePlatformEdit); - saveProfileButton.addEventListener("click", finalizeName); - - pfpStack.addEventListener("drop", (dropEvent) => { - if (upload.classList.contains("active")) { - dropEvent.preventDefault(); - pfpStack.classList.remove("hover"); - pfpStack.classList.remove("invalid"); - if (dropEvent.dataTransfer.files) { - const file = dropEvent.dataTransfer.files[0]; - if (validImageFile(file.type)) { - const data = new FormData(); - data.append('session', new Blob([`{ "key": "${session.key}" }`], { type: `application/json` })); - data.append('file', file); - - fetch(`/api/user/${profileId}/image`, { - method: 'POST', - body: data - }).then(response => { - if (upload.classList.contains("active")) { - if (response.status === 201) { - pfp.src = `/api/user/${profileId}/image?time=${Date.now()}`; - } else { - pfpStack.classList.add("invalid"); - } - } - }); - return; - } - } - pfpStack.classList.add("invalid"); - } - }); - pfpStack.addEventListener("dragover", (dragEvent) => { - if (upload.classList.contains("active")) { - dragEvent.preventDefault(); - } - }); - pfpStack.addEventListener("dragenter", (dragEvent) => { - if (upload.classList.contains("active")) { - pfpStack.classList.remove("hover"); - pfpStack.classList.remove("invalid"); - if (dragEvent.dataTransfer.types.includes("application/x-moz-file")) { - pfpStack.classList.add("hover"); - } else { - pfpStack.classList.add("invalid"); - } - } - }); - pfpStack.addEventListener("dragleave", (dragEvent) => { - if (upload.classList.contains("active")) { - pfpStack.classList.remove("hover"); - pfpStack.classList.remove("invalid"); - } - }); - } - - { - const editPlatformsButton = document.querySelector("#platform-edit-stack"); - const savePlatformsButton = document.querySelector("#platform-save-stack"); - const platforms = document.querySelectorAll("#profile-platforms .profile-entry"); - - const togglePlatformEdit = (clickEvent) => { - editPlatformsButton.classList.toggle("active"); - savePlatformsButton.classList.toggle("active"); - for (const platform of platforms) { - platform.classList.toggle("editing"); - } - checkLists(); - }; - editPlatformsButton.addEventListener("click", togglePlatformEdit); - savePlatformsButton.addEventListener("click", togglePlatformEdit); - - const steamButtons = [ - document.querySelector("#add-steam"), - document.querySelector("#platform-0"), - ]; - - let allowSteamImport = true; - steamButtons[0].addEventListener("click", (clickEvent) => { - if (allowSteamImport) { - window.location.href = "/auth/steam"; - } - }); - steamButtons[1].addEventListener("click", (clickEvent) => { - fetch(`/api/user/${profileId}/platforms/remove`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ sessionKey: session.key, platformId: 0 }) - }).then(() => { - allowSteamImport = true; - }); - allowSteamImport = false; - steamButtons[1].parentElement.classList.remove("connected"); - }); - - if (isReturn) { - editPlatformsButton.click(); - } - } - - // Canvasing - - const completionCanvas = document.querySelector("#profile-completion-canvas"); - - const STROKE_WIDTH = 0.18; - const style = window.getComputedStyle(completionCanvas); - const context = completionCanvas.getContext('2d'); - - const drawCanvas = () => profileData.then(data => { - const width = Number(style.getPropertyValue('width').slice(0, -2)); - const height = width; - - context.canvas.width = width; - context.canvas.height = height; - context.clearRect(0, 0, width, height); - context.strokeStyle = root.getProperty('--accent-value3'); - context.lineWidth = (width / 2) * STROKE_WIDTH; - context.beginPath(); - context.arc(width / 2, height / 2, (width / 2) * (1 - STROKE_WIDTH / 2), -0.5 * Math.PI, (-0.5 + (data.average === null ? 0 : (data.average / 100) * 2)) * Math.PI); - context.stroke(); - }); - - window.addEventListener('resize', drawCanvas); - drawCanvas(); - - if (profileId === session.id) { - document.querySelector("#profile-page").classList.add("self"); - } - - { - const noteworthy = document.querySelectorAll(".list-page-entry.achievement"); - for (const achievement of noteworthy) { - achievement.addEventListener("click", (clickEvent) => { - window.location.href = `/achievement/${achievement.dataset.id}`; - }); - } - } - - { - const ratings = document.querySelectorAll(".list-page-entry.rating"); - for (const rating of ratings) { - rating.addEventListener("click", (clickEvent) => { - window.location.href = `/achievement/${rating.dataset.id}`; - }); - } - } -} - -const expandTemplates = async () => { - await commonTemplates(); - template.apply("profile-page").promise(profileData.then(data => ({ - id: profileId, - username: data.username, - completed: data.completed, - average: data.average === null ? "N/A" : data.average + "%", - perfect: data.perfect, - }))); - template.apply("profile-noteworthy-list").promise( - fetch(`/api/user/${profileId}/noteworthy`, { - method: 'GET' - }) - .then(response => response.json()) - .then(data => data.map(data => ({ - achievement_id: data.ID, - achievement_name: data.name, - completion: data.completion - })) - ) - ); - template.apply("profile-platforms-list").promise(profileData.then(data => - data.platforms.map(platform => ({ - platform_id: platform.id, - img: `Steam Logo`, - name: platform.name, - connected: platform.connected ? "connected" : "", - add: - (platform.id === 0 ? `Add` : - (platform.id === 1 ? `

Coming soon...

` : - (platform.id === 2 ? `

Coming soon...

` : - ""))) - })) - )); - template.apply("rating-list").promise(profileData.then(data => data.ratings.map(data => ({ - achievement_id: data.achievementId, - rating_achievement: data.name, - rating_difficulty: data.difficulty, - rating_quality: data.quality, - rating_review: data.review - })))); -} - -window.addEventListener("load", async (loadEvent) => { - await loadCommon(); - - var importing = document.querySelector("#importing"); - if (!/\d+/.test(profileId)) { - isReturn = true; - const platform = profileId; - if (!session.key) { - window.location.href = "/404"; - } else { - profileId = session.lastProfile; - 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); - - if (query.get('openid.mode') === 'cancel') { - - } 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/${profileId}/platforms/add`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ sessionKey: session.key, userId: profileId, platformId: 0, platformUserId: `${steamId}` }) - }); - } - } - - window.history.replaceState({}, '', `/profile/${profileId}`); - } else if (/\d+/.test(profileId)) { - profileId = Number(profileId); - if (session.key) { - session.lastProfile = profileId; - } - } else { - // Handle error - } - importing.remove(); - - profileData = fetch(`/api/user/${profileId}`, { method: 'GET' }) - .then(response => response.json()); - - await expandTemplates(); - await template.expand(); - - loadLazyImages(); - connectNavbar(); - loadProfile(); -}); \ No newline at end of file diff --git a/frontend/webpage/static/styles/achievement.css b/frontend/webpage/static/styles/achievement.css deleted file mode 100644 index e062d46..0000000 --- a/frontend/webpage/static/styles/achievement.css +++ /dev/null @@ -1,459 +0,0 @@ -#achievement-page { - 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; -} - -.achievement-list { - width: 100%; - height: max-content; - - border-radius: 8px; - - overflow: hidden; - - box-shadow: 0px 0px 8px 8px var(--shadow-color); -} - -.achievement-entry { - overflow: hidden; - - height: 64px; - - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - - background-color: var(--distinction); -} - -.achievement-entry-left { - height: 100%; - - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; -} - -.achievement-entry-right { - height: 100%; - - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; -} - -.achievement-entry-icon { - width: 64px; - - flex-grow: 0; -} - -.achievement-entry-text { - box-sizing: border-box; - - margin: 0; - padding: 0px 16px; - - height: 100%; - line-height: 64px; - - color: var(--foreground); - - font-size: 24px; - - border-top: 1px solid var(--background); - - flex-basis: max-content; - flex-grow: 0; -} - -.top > .achievement-entry-text { - border: 0; -} - -.achievement-entry-text.platform-name { - flex-grow: 1; -} - -#achievement-info { - height: max-content; -} - -#achievement-info-flex { - justify-content: flex-start; - align-items: flex-end; -} - -#achievement-name-text { - flex-grow: 0; - width: max-content; -} - -#achievement-icon-img { - flex-grow: 0; - margin-bottom: 0.25em; - margin-left: 32px; - - font-size: 32px; - width: 64px; - height: 64px; - object-fit: contain; - - background-color: var(--background-dark); - - position: relative; -} - -#achievement-description-text { - font-size: 24px; - color: var(--foreground); - background: var(--distinction); - margin: 0; - padding: 16px; -} - -#achievement-section-1 { - box-sizing: border-box; - - width: 100%; - height: max-content; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; -} - -#achievement-stats { - flex-basis: 0; - flex-grow: 1; - - display: flex; - flex-direction: row; - justify-content: center; -} - -#achievement-stats-numeric { - flex-grow: 1; - width: 100%; - max-width: 800px; - - display: flex; - flex-direction: row; - justify-content: space-between; -} - -#achievement-completion-stack { - flex-grow: 1; - height: max-content; - position: relative; -} - -#achievement-completion-background { - width: 100%; - display: block; -} - -#achievement-completion-canvas { - width: 100%; - height: 100%; - - position: absolute; - left: 0; - top: 0; -} - -#achievement-completion-text { - margin: 0; - - width: 100%; - height: 100%; - - color: var(--foreground); - font-size: 64px; - - position: absolute; - left: 0; - top: 0; - - display: flex; - justify-content: center; - align-items: center; -} - -#achievement-difficulty, -#achievement-quality { - flex-grow: 1; -} - -#achievement-difficulty-text { - margin: 0; - - height: 48px; - - color: var(--foreground); - - font-size: 48px; - line-height: 48px; - text-align: center; -} - -#achievement-quality-text { - margin: 0; - - height: 48px; - - color: var(--foreground); - - font-size: 48px; - line-height: 48px; - text-align: center; -} - -#achievement-rating { - flex-basis: 0; - flex-grow: 1; -} - -.achievement-save-stack { - display: none; -} - -.achievement-save-stack.active { - display: block; - width: max-content; - height: max-content; -} - -.achievement-save-stack:hover .achievement-save, -.achievement-save-hover { - display: none; -} - -.achievement-save, -.achievement-save-stack:hover .achievement-save-hover { - display: block; -} - -#achievement-rating-subsection { - background-color: var(--distinction); -} - -#achievement-rating-numeric { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} - -.achievement-rating-text-flex { - display: flex; - flex-direction: row; - align-items: center; -} - -#achievement-difficulty-rating, -#achievement-quality-rating { - flex-grow: 1; -} - -.achievement-rating-text { - flex-basis: max-content; - flex-grow: 1; -} - -.achievement-rating-max-text { - flex-grow: 0; - - margin: 0; - margin-left: 16px; - font-size: 24px; - - color: var(--foreground); -} - -#achievement-difficulty-rating-text, -#achievement-quality-rating-text, -#achievement-review-rating-text { - box-sizing: border-box; - padding: 8px; - font-size: 20px; - color: var(--background); - border: 0; - outline: none; - - border-radius: 8px; -} - -#achievement-review-rating-text { - width: 100%; - height: 96px; - - resize: none; -} - - -#profile-section-2 { - box-sizing: border-box; - - width: 100%; - height: max-content; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; -} - -#profile-hardest { - flex-grow: 1; - height: 100%; -} - -#profile-platforms { - flex-grow: 1; -} - -#profile-platforms .profile-entry { - display: none; -} - -#profile-platforms .profile-entry-text { - color: var(--foreground); -} - -#profile-platforms .profile-entry.connected, -#profile-platforms .profile-entry.editing { - display: flex; -} - -#profile-page .profile-edit-stack, -#profile-page .profile-save-stack { - display: none; -} - -#profile-page.self .profile-edit-stack, -#profile-page.self .profile-save-stack.active { - display: block; - width: max-content; - height: max-content; -} - -#profile-page.self .profile-edit-stack.active, -#profile-page.self .profile-save-stack { - display: none; -} - -.profile-edit-stack:hover > .profile-edit-hover, -.profile-edit { - display: block; -} - -.profile-edit-stack:hover > .profile-edit, -.profile-edit-hover { - display: none; -} - -.profile-save-stack:hover > .profile-save-hover, -.profile-save { - display: block; -} - -.profile-save-stack:hover > .profile-save, -.profile-save-hover { - display: none; -} - -.profile-entry .platform-remove-stack, -.profile-entry .platform-add, -.profile-entry .platform-unsupported, -.profile-entry.connected.editing .platform-add { - border-top: 1px solid var(--background); - display: none; -} - -.profile-entry.connected.editing .platform-remove-stack { - box-sizing: border-box; - display: block; - height: 100%; - width: max-content; - flex-grow: 0; -} - -.platform-remove, .platform-remove-hover { - box-sizing: border-box; - padding: 12px; - height: 100%; -} - -.profile-entry.connected.editing .platform-remove-stack .platform-remove, -.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove-hover { - display: block; -} - -.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove, -.profile-entry.connected.editing .platform-remove-stack .platform-remove-hover { - display: none; -} - -.profile-entry .platform-add { - box-sizing: border-box; - height: 100%; - padding: 16px 8px; -} - -.profile-entry.editing .platform-add { - display: block; -} - -.profile-entry.editing .platform-unsupported { - box-sizing: border-box; - display: block; - margin: 0; - padding: 0% 2%; - line-height: 63px; - font-size: 24px; - color: var(--foreground-disabled); -} - -#profile-ratings { - flex-grow: 1; -} - -.list-page-entry.rating:hover { - background-color: var(--background-light); -} - -.list-page-entry-text.rating-username { flex-grow: 1.5; } -.list-page-entry-text.rating-difficulty { flex-grow: 1; } -.list-page-entry-text.rating-quality { flex-grow: 1; } -.list-page-entry-text.rating-review { flex-grow: 5; } - -.list-page-header > .list-page-entry-text.rating-username:hover, -.list-page-header > .list-page-entry-text.rating-difficulty:hover, -.list-page-header > .list-page-entry-text.rating-quality:hover, -.list-page-header > .list-page-entry-text.rating-review:hover { - background-color: var(--accent-value2); -} \ No newline at end of file diff --git a/frontend/webpage/static/styles/common.css b/frontend/webpage/static/styles/common.css deleted file mode 100644 index 1f72ac5..0000000 --- a/frontend/webpage/static/styles/common.css +++ /dev/null @@ -1,592 +0,0 @@ -:root { - --shadow-color: rgba(0, 0, 0, 0.5); -} - -html, body { - background-color: var(--background-dark); - - margin: 0; - border: 0; - padding: 0; - width: 100%; - height: 100%; - - font-family: sans-serif; -} - -input { - font-family: sans-serif; -} - -textarea { - font-family: sans-serif; -} - -#navbar { - z-index: 1; - - position: fixed; - - background-color: var(--accent-value2); - color: var(--foreground); - - width: 100%; - min-height: 76px; - height: 5%; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - box-shadow: 0px 0px 5px 10px var(--shadow-color); -} - -.navbar-section { - width: max-content; - height: 100%; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; -} - -.navbar-item { - box-sizing: border-box; - padding: 0px 20px; - - width: max-content; - height: 100%; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - - font-size: 24px; - - user-select: none; - - transition-property: background-color; - transition-duration: 0.15s; - - position: relative; -} - -.navbar-item:hover { - 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); - - border-radius: 4px; - - cursor: default; - - display: flex; - justify-content: center; - align-items: center; - - transition-property: background-color; - transition-duration: 0.15s; -} - -.ap-button:hover { - background-color: var(--accent-value3); -} - -.ap-button:active { - background-color: var(--accent-value1); -} - -.ap-button.disabled { - background-color: var(--accent-value1); - color: var(--accent-value0); -} - -.ap-button.disabled:hover { - background-color: var(--accent-value1); - color: var(--accent-value0); -} - -.ap-button.disabled:active { - background-color: var(--accent-value1); - color: var(--accent-value0); -} - -#content-body { - position: relative; - - top: max(76px, 5%); - - width: 100%; - height: calc(100% - max(76px, 5%)); - - overflow-y: auto; - - display: flex; - justify-content: center; -} - -.page { - box-sizing: border-box; - - padding: 32px; - - width: 100%; - height: max-content; - min-height: 100%; - - background-color: var(--background); - box-shadow: 0px 0px 5px 10px var(--shadow-color); -} - -.page-subsection { - box-sizing: border-box; - margin: 32px; - padding: 16px; - - background-color: var(--background-dark); - - border-radius: 8px; - - box-shadow: inset 0px 0px 8px 8px var(--shadow-color); -} - -.page-subsection-wrapper { - padding: 16px; -} - -.page-subsection-chunk { - box-shadow: 0px 0px 8px 8px var(--shadow-color); - - border-radius: 8px; - overflow: hidden; -} - -.page-header { - box-sizing: border-box; - - height: max-content; - - margin: 16px; -} - -.page-subheader { - margin-bottom: 16px; - - width: 100%; -} - -.page-subheader-flex { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; -} - -.page-header-text, -.page-subheader-text { - margin: 0 0 0.25em; - - color: var(--foreground); - - cursor: default; - - overflow: hidden; - text-overflow: ellipsis; - flex-grow: 1; -} - -.page-header-text.link, -.page-subheader-text.link { - transition-property: color; - transition-duration: 0.15s; -} - -.page-header-text.link:hover, -.page-subheader-text.link:hover { - color: var(--accent-value4); -} - -.page-header-text { - font-size: 48px; -} - -.page-subheader-text, -.page-subheader-icon { - font-size: 32px; -} - -.page-subheader-icon { - margin: 0 0 0.25em; - height: 32px; - padding: 0; -} - -.page-header-separator, -.page-subheader-separator { - width: 100%; - height: 3px; - - background-color: var(--accent-value3); -} - -#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; - - transition-property: background-color; - transition-duration: 0.15s; -} - -.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; - - cursor: default; -} - -.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); - - transition-property: background-color; - transition-duration: 0.15s; -} - -.list-page-header > .list-page-entry-text:hover { - background-color: var(--accent-value1); -} - -.list-page-header > .list-page-entry-text:active { - background-color: var(--accent-value0); -} - -#loading-results { - margin: 16px 0; - width: 100%; - height: 64px; - object-fit: contain; - - display: none; -} \ No newline at end of file diff --git a/frontend/webpage/static/styles/import.css b/frontend/webpage/static/styles/import.css deleted file mode 100644 index 98d69a1..0000000 --- a/frontend/webpage/static/styles/import.css +++ /dev/null @@ -1,78 +0,0 @@ -#import-page { - display: flex; - flex-direction: column; -} - -#import-dropzone { - flex-grow: 1; - - overflow-y: auto; -} - -#import-dropzone-wrapper { - box-sizing: border-box; - - width: 100%; - height: 100%; -} - -#upload-wrapper { - display: flex; - justify-content: center; - align-items: center; - - width: 100%; - height: 100%; -} - -#upload-icon-stack { - width: max-content; - height: max-content; - - position: relative; -} - -#import-icon-base, -#import-icon-hover { - width: 256px; - height: 256px; - display: block; -} - -#import-icon-hover { - position: absolute; - left: 0; - top: 0; -} - -#import-dropzone.active #import-icon-base, -#import-icon-hover { - visibility: hidden; -} - -#import-icon-base, -#import-dropzone.active #import-icon-hover { - visibility: visible; -} - -#import-console { - display: none; -} - -.console-entry { - box-sizing: border-box; - margin: 0; - padding-top: 0.25em; - height: max-content; - - color: var(--foreground); - font-size: 24px; - font-family: 'Ubuntu Mono', monospace; - white-space: nowrap; - text-overflow: ellipsis; - overflow-x: hidden; -} - -.console-entry.top { - padding-top: 0; -} \ No newline at end of file diff --git a/frontend/webpage/static/styles/login.css b/frontend/webpage/static/styles/login.css deleted file mode 100644 index 84c99d5..0000000 --- a/frontend/webpage/static/styles/login.css +++ /dev/null @@ -1,125 +0,0 @@ -:root { - --form-spacing: 48px; - - --element-spacing: 12px; -} - -#login-page { - display: block; - - max-width: 1280px; -} - -#login-flex { - display: flex; - flex-direction: row; - justify-content: center; -} - -#login-subsection { - width: 100%; -} - -#login-elements { - display: flex; - flex-direction: column; - align-items: center; -} - -#login-header { - box-sizing: border-box; - - width: 50%; - height: max-content; -} - -#login-form { - box-sizing: border-box; - - padding: 24px 0; - - width: 50%; - height: max-content; - - background-color: var(--distinction); - - border-radius: 8px; -} - -.form-row { - margin: var(--element-spacing) var(--form-spacing) 0; - border: 0; - padding: 0; - width: calc(100% - var(--form-spacing) * 2); - height: 40px; -} - -.form-row.top { - margin-top: 0; -} - -.form-row.multiline { - height: max-content; -} - -#error-message { - display: none; - - line-height: 40px; - - color: var(--error); - font-size: 20px; -} - -.login-field { - box-sizing: border-box; - padding: 0 var(--element-spacing); - font-size: 18px; - border-radius: 4px; - outline: none; -} - -.login-field.error { - background-color: var(--error); -} - -.ap-button.login { - height: 40px; - - font-size: 18px; -} - -#username, -#confirm { - display: none; -} - -#button-row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} - -#create-user-button, -#guest-login-button { - width: calc(50% - var(--element-spacing) / 2); -} - -#login-button { - width: calc(100% - var(--form-spacing * 2)) -} - -#warning { - color: var(--error); - font-size: 24px; - text-align: center; - line-height: 40px; -} - -#warning-message { - margin-top: 0; - color: var(--foreground); - font-size: 18px; - text-align: center; -} \ No newline at end of file diff --git a/frontend/webpage/static/styles/search.css b/frontend/webpage/static/styles/search.css deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/webpage/static/styles/search_achievements.css b/frontend/webpage/static/styles/search_achievements.css deleted file mode 100644 index 72f84d5..0000000 --- a/frontend/webpage/static/styles/search_achievements.css +++ /dev/null @@ -1,9 +0,0 @@ -.list-page-entry.achievement:hover { - background-color: var(--background-light); -} - -.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; } \ No newline at end of file diff --git a/frontend/webpage/static/styles/search_games.css b/frontend/webpage/static/styles/search_games.css deleted file mode 100644 index 525cab5..0000000 --- a/frontend/webpage/static/styles/search_games.css +++ /dev/null @@ -1,9 +0,0 @@ -.list-page-entry-icon.game { - width: 137px; -} - -.list-page-entry-text.game-name { flex-grow: 2.5; } -.list-page-entry-text.game-achievement-count { flex-grow: 1; } -.list-page-entry-text.game-avg-completion { flex-grow: 1; } -.list-page-entry-text.game-num-owners { flex-grow: 1; } -.list-page-entry-text.game-num-perfects { flex-grow: 1; } \ No newline at end of file diff --git a/frontend/webpage/static/styles/search_users.css b/frontend/webpage/static/styles/search_users.css deleted file mode 100644 index a1c116c..0000000 --- a/frontend/webpage/static/styles/search_users.css +++ /dev/null @@ -1,9 +0,0 @@ -.list-page-entry.user:hover { - background-color: var(--background-light); -} - -.list-page-entry-text.user-username { flex-grow: 1.5; } -.list-page-entry-text.user-game-count { flex-grow: 1; } -.list-page-entry-text.user-achievement-count { flex-grow: 1; } -.list-page-entry-text.user-avg-completion { flex-grow: 1; } -.list-page-entry-text.user-perfect-games { flex-grow: 1; } \ No newline at end of file diff --git a/frontend/webpage/static/styles/theme.css b/frontend/webpage/static/styles/theme.css deleted file mode 100644 index cc4b394..0000000 --- a/frontend/webpage/static/styles/theme.css +++ /dev/null @@ -1,24 +0,0 @@ -:root { - --background-dark: #111117; - --background: #22222A; - --background-light: #33333B; - --distinction: #44444C; - --foreground-disabled: #77777D; - --foreground-dark: #AAAAAA; - --foreground: #EEEEEE; - - --accent-hue: 0; - - --accent-value0: hsl(var(--accent-hue), 100%, 16%); - --accent-value1: hsl(var(--accent-hue), 100%, 25%); - --accent-value2: hsl(var(--accent-hue), 100%, 31%); - --accent-value3: hsl(var(--accent-hue), 73%, 47%); - --accent-value4: hsl(var(--accent-hue), 83%, 57%); - --accent-value5: hsl(var(--accent-hue), 93%, 72%); - --accent-pure: hsl(var(--accent-hue), 100%, 50%); - - --selected-accent0: #2266CC; - --selected-accent1: #3388FF; - - --error: #F95959; -} diff --git a/frontend/webpage/static/styles/user.css b/frontend/webpage/static/styles/user.css deleted file mode 100644 index 7f4e1c5..0000000 --- a/frontend/webpage/static/styles/user.css +++ /dev/null @@ -1,448 +0,0 @@ -#profile-page { - 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; - - border-radius: 8px; - - overflow: hidden; - - box-shadow: 0px 0px 8px 8px var(--shadow-color); -} - -.profile-entry { - overflow: hidden; - - height: 64px; - - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - - background-color: var(--distinction); -} - -.profile-entry-left { - height: 100%; - - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; -} - -.profile-entry-right { - height: 100%; - - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; -} - -.profile-entry-icon { - width: 64px; - - flex-grow: 0; -} - -.profile-entry-text { - box-sizing: border-box; - - margin: 0; - padding: 0px 16px; - - height: 100%; - line-height: 64px; - - color: var(--foreground); - - font-size: 24px; - - border-top: 1px solid var(--background); - - flex-basis: max-content; - flex-grow: 0; -} - -.top > .profile-entry-text { - border: 0; -} - -.profile-entry-text.platform-name { - flex-grow: 1; -} - -#profile-section-1 { - box-sizing: border-box; - - width: 100%; - height: max-content; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; -} - -#profile-left { - widtH: 50%; - height: max-content; - - max-width: 512px; -} - -#profile-info-username-text.active, -#profile-info-username-field { - display: none; -} - -#profile-info-username-field.active { - display: block; -} - -#profile-info-username-field { - margin-right: 8px; - padding: 4px; - font-size: 24px; - color: var(--background); - background-color: var(--foreground); - - border-radius: 8px; - - border: 0; - outline: none; -} - -#profile-info-pfp-border { - box-sizing: border-box; - - padding: 24px; - background-color: var(--distinction); - - width: 100%; - height: max-content; - - box-shadow: 0px 0px 8px 8px var(--shadow-color); - - border-radius: 8px; -} - -#profile-info-pfp { - top: 0; - left: 0; - width: 100%; - height: 100%; - - border-radius: 8px; - - position: relative; -} - -#profile-info-pfp-img { - display: block; - width: 100%; - height: 100%; - border-radius: 8px; - object-fit: contain; - - background-color: var(--background-dark); - - position: absolute; -} - -#profile-info-pfp-vignette { - top: 0; - left: 0; - width: 100%; - height: 100%; - - box-shadow: inset 0px 0px 30px 10px var(--shadow-color); - - border-radius: 8px; - - position: absolute; - z-index: 1; -} - -#profile-info-pfp-upload, -#profile-info-pfp-upload-hover, -#profile-info-pfp-upload-invalid { - top: 0; - left: 0; - width: 100%; - - border-radius: 8px; - - background-color: var(--background-dark); - opacity: 0.8; - - display: block; - - visibility: hidden; -} - -#profile-info-pfp-upload { - position: relative; -} - -#profile-info-pfp-upload-hover, -#profile-info-pfp-upload-invalid { - position: absolute; -} - -#profile-info-pfp #profile-info-pfp-upload.active, -#profile-info-pfp.hover #profile-info-pfp-upload-hover.active, -#profile-info-pfp.invalid #profile-info-pfp-upload-invalid.active { - visibility: visible; -} - -#profile-info-pfp.hover #profile-info-pfp-upload.active, -#profile-info-pfp.invalid #profile-info-pfp-upload.active { - visibility: hidden; -} - -#profile-stats { - flex-grow: 1; - - display: flex; - flex-direction: row; -} - -#profile-stats-numeric { - flex-grow: 1; - max-width: 300px; - - display: flex; - flex-direction: column; - justify-content: space-between; -} - -#profile-completion-stack { - width: 100%; - height: max-content; - position: relative; -} - -#profile-completion-background { - width: 100%; - display: block; -} - -#profile-completion-canvas { - width: 100%; - height: 100%; - - position: absolute; - left: 0; - top: 0; -} - -#profile-completion-text { - margin: 0; - - width: 100%; - height: 100%; - - color: var(--foreground); - font-size: 64px; - - position: absolute; - left: 0; - top: 0; - - display: flex; - justify-content: center; - align-items: center; -} - -#profile-perfect-text { - margin: 0; - - height: 48px; - - color: var(--foreground); - - font-size: 48px; - line-height: 48px; - text-align: center; -} - -#profile-section-2 { - box-sizing: border-box; - - width: 100%; - height: max-content; - - display: flex; - flex-direction: row; - justify-content: center; - align-items: flex-start; -} - -#profile-hardest { - flex-grow: 1; - height: 100%; -} - -#profile-platforms .profile-entry { - display: none; -} - -#profile-platforms .profile-entry-text { - color: var(--foreground); -} - -#profile-platforms .profile-entry.connected, -#profile-platforms .profile-entry.editing { - display: flex; -} - -#profile-page .profile-edit-stack, -#profile-page .profile-save-stack { - display: none; -} - -#profile-page.self .profile-edit-stack, -#profile-page.self .profile-save-stack.active { - display: block; - width: max-content; - height: max-content; -} - -#profile-page.self .profile-edit-stack.active, -#profile-page.self .profile-save-stack { - display: none; -} - -.profile-edit-stack:hover > .profile-edit-hover, -.profile-edit { - display: block; -} - -.profile-edit-stack:hover > .profile-edit, -.profile-edit-hover { - display: none; -} - -.profile-save-stack:hover > .profile-save-hover, -.profile-save { - display: block; -} - -.profile-save-stack:hover > .profile-save, -.profile-save-hover { - display: none; -} - -.profile-entry .platform-remove-stack, -.profile-entry .platform-add, -.profile-entry .platform-unsupported, -.profile-entry.connected.editing .platform-add { - border-top: 1px solid var(--background); - display: none; -} - -.profile-entry.connected.editing .platform-remove-stack { - box-sizing: border-box; - display: block; - height: 100%; - width: max-content; - flex-grow: 0; -} - -.platform-remove, .platform-remove-hover { - box-sizing: border-box; - padding: 12px; - height: 100%; -} - -.profile-entry.connected.editing .platform-remove-stack .platform-remove, -.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove-hover { - display: block; -} - -.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove, -.profile-entry.connected.editing .platform-remove-stack .platform-remove-hover { - display: none; -} - -.profile-entry .platform-add { - box-sizing: border-box; - height: 100%; - padding: 16px 8px; -} - -.profile-entry.editing .platform-add { - display: block; -} - -.profile-entry.editing .platform-unsupported { - box-sizing: border-box; - display: block; - margin: 0; - padding: 0% 2%; - line-height: 63px; - font-size: 24px; - color: var(--foreground-disabled); -} - -#profile-ratings { - width: 100%; -} - -.list-page-entry.achievement:hover { - background-color: var(--background-light); -} - -.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; } - -.list-page-header > .list-page-entry-text.-aichevevement-name:hover, -.list-page-header > .list-page-entry-text.-aichevevement-completion:hover, -.list-page-header > .list-page-entry-text.-aichevevement-quality:hover, -.list-page-header > .list-page-entry-text.-aichevevement-difficulty:hover { - background-color: var(--accent-value2); -} - -.list-page-entry.rating:hover { - background-color: var(--background-light); -} - -.list-page-entry-text.rating-achievement { flex-grow: 2; } -.list-page-entry-text.rating-difficulty { flex-grow: 1; } -.list-page-entry-text.rating-quality { flex-grow: 1; } -.list-page-entry-text.rating-review { flex-grow: 5; } - -.list-page-header > .list-page-entry-text.rating-achievement:hover, -.list-page-header > .list-page-entry-text.rating-difficulty:hover, -.list-page-header > .list-page-entry-text.rating-quality:hover, -.list-page-header > .list-page-entry-text.rating-review:hover { - background-color: var(--accent-value2); -} \ No newline at end of file diff --git a/frontend/webpage/styles/index.css b/frontend/webpage/styles/index.css new file mode 100644 index 0000000..a9fe898 --- /dev/null +++ b/frontend/webpage/styles/index.css @@ -0,0 +1,707 @@ +:root { + --background-dark: #111115; + --background: #22222A; + --foreground: #EEEEEE; + --distinction: #44444F; + + --accent-value0: #500000; + --accent-value1: #800000; + --accent-value2: #A00000; + --accent-value3: #D02020; + --accent-value4: #FA7575; + + --selected-accent0: #0066CC; + --selected-accent1: #3388FF; + + --navbar-background: var(--accent-value2); + --navbar-hover-background: var(--accent-value3); + --navbar-foreground: #EEEEEE; + + --header-color: var(--accent-value3); +} + +html, body { + background-color: var(--background-dark); + + margin: 0; + border: 0; + padding: 0; + width: 100%; + height: 100%; + + font-family: sans-serif; +} + +#navbar { + z-index: 1; + + position: fixed; + + background-color: var(--navbar-background); + color: var(--navbar-foreground); + + width: 100%; + min-height: 76px; + height: 5%; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5); +} + +.navbar-section { + width: max-content; + height: 100%; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.navbar-item { + box-sizing: border-box; + padding: 0px 20px; + + width: max-content; + height: 100%; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + font-size: 24px; + + user-select: none; + + transition-property: background-color; + transition-duration: 0.15s; + + position: relative; +} + +.navbar-item:hover { + background-color: var(--navbar-hover-background); +} + +#content-body { + position: relative; + + top: max(76px, 5%); + + width: 100%; + height: calc(100% - max(76px, 5%)); + + overflow-y: auto; + + display: flex; + justify-content: center; +} + +.page { + z-index: 0; + + box-sizing: border-box; + + padding: 0px 64px; + + width: 100%; + height: 100%; + + background-color: var(--background); + box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5); + + overflow-y: auto; + + display: none; +} + +.page-header { + box-sizing: border-box; + + padding: 64px 0px; + + width: 100%; + height: max-content; +} + +.page-header-text { + width: max-content; + + margin: 0; + margin-bottom: 0.25em; + + color: var(--header-color); + + font-size: 64px; +} + +.page-header-separator { + width: 100%; + height: 3px; + + background-color: var(--foreground); +} + +.page-subheader-text { + width: max-content; + + margin: 0; + margin-bottom: 0.25em; + + color: var(--header-color); + + font-size: 48px; + + user-select: none; +} + +.page-subheader-separator { + width: 100%; + height: 3px; + + background-color: var(--foreground); + + transition-property: color; + transition-duration: 0.15s; +} + +.list-page-search { + box-sizing: border-box; + + padding: 32px 64px; + + width: 100%; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.list-page-search > label { + box-sizing: border-box; + padding: 16px 24px; + + background-color: var(--accent-value2); + + color: var(--foreground); + + font-size: 32px; + + border-radius: 8px 0px 0px 8px; + + transition-property: background-color; + transition-duration: 0.15s; +} + +.list-page-search > label:hover { + background-color: var(--accent-value3); +} + +.list-page-search > label:active { + background-color: var(--accent-value1); +} + +.list-page-search > input { + box-sizing: border-box; + padding: 16px 24px; + + background-color: var(--distinction); + + color: var(--foreground); + + font-size: 32px; + + border: 0; + border-radius: 0px 8px 8px 0px; + + 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; + + padding: 32px 64px; + + 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 { + margin-top: 16px; + + 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: 32px; + height: 32px; + + background-color: var(--background); + + border: 3px solid var(--distinction); + border-radius: 8px; + + transition-property: background-color, border-color; + transition-duration: 0.15s; +} + +.list-page-filter:hover > .list-page-filter-checkbox { + background-color: var(--background); + 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; + + padding-left: 64px; + + flex-grow: 1; +} + +.list-page-list { + border-radius: 8px; + + overflow: hidden; + overflow-y: auto; +} + +.list-page-list-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-list-entry { + width: 100%; + height: 64px; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + color: var(--foreground); + font-size: 24px; + + border-bottom: 1px solid var(--distinction); +} + +#games-page { + max-width: 1920px; +} + +.game-list-page-entry-icon { + width: 64px; + height: 64px; + + flex-grow: 0; +} + +.game-list-page-entry-name { + box-sizing: border-box; + + margin: 0; + padding: 0 12px; + width: 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + flex-grow: 1; + flex-basis: 0px; +} + +.game-list-page-entry-description { + box-sizing: border-box; + + margin: 0; + padding: 0 12px; + width: 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + flex-grow: 2; + flex-basis: 0px; +} + +#achievements-page { + max-width: 1920px; +} + +.achievement-list-page-entry-icon { + width: 64px; + height: 64px; + + flex-grow: 0; +} + +.achievement-list-page-entry-name { + box-sizing: border-box; + + margin: 0; + padding: 0 12px; + width: 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + flex-grow: 4; + flex-basis: 0px; +} + +.achievement-list-page-entry-description { + box-sizing: border-box; + + margin: 0; + padding: 0 12px; + width: 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + flex-grow: 8; + flex-basis: 0px; +} + +.achievement-list-page-entry-stages { + box-sizing: border-box; + + margin: 0; + padding: 0 12px; + width: 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + flex-grow: 1; + flex-basis: 0px; +} + +#profile-page { + max-width: 1920px; + + display: block; +} + +#profile-section-1 { + box-sizing: border-box; + + width: 100%; + height: max-content; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: flex-start; +} + +#profile-info { + box-sizing: border-box; + + width: max-content; + height: max-content; + + max-width: 50%; + + padding: 0px 64px; + padding-bottom: 128px; +} + +#profile-info-pfp { + width: 100%; + max-width: 640px; +} + +#profile-info-name { + margin: 0; + + padding: 16px 0px; + + font-size: 42px; + + color: var(--header-color); +} + +#profile-platforms { + box-sizing: border-box; + + height: max-content; + + padding: 0px 64px; + + flex-grow: 1; +} + +.profile-platform-entry { + overflow: hidden; + + margin-top: 16px; + + height: 64px; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + border: 3px solid var(--distinction); + border-radius: 12px; +} + +.profile-platform-entry.connected { + border: 3px solid var(--accent-value3); + + background-color: var(--accent-value2); +} + +.profile-platform-entry-left { + height: 100%; + + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; +} + +.profile-platform-entry-right { + height: 100%; + + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; +} + +.profile-platform-icon { + width: 64px; +} + +.profile-platform-name { + margin: 0; + padding: 0px 16px; + + color: var(--foreground); + + font-size: 24px; +} + +.profile-platform-connected { + display: none; + + margin: 0; + padding: 0px 16px; + + color: var(--foreground); + + font-size: 24px; +} + +.profile-platform-entry.connected .profile-platform-connected { + display: block; +} + +#profile-section-2 { + box-sizing: border-box; + + width: 100%; + height: max-content; + + display: flex; + flex-direction: row; + justify-content: center; + align-items: flex-start; +} + +#profile-games { + box-sizing: border-box; + padding: 0px 64px; + + width: 50%; + height: max-content; +} + +#profile-games > .page-subheader-text:hover { + color: var(--accent-value4); +} + +.profile-game-entry { + overflow: hidden; + + margin-top: 16px; + + height: 64px; + + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + border: 3px solid var(--distinction); + border-radius: 12px; +} + +.profile-game-entry-icon { + height: 64px; +} + +.profile-game-entry-name { + margin: 0; + padding: 0px 16px; + + color: var(--foreground); + + font-size: 24px; +} + +#profile-achievements { + box-sizing: border-box; + padding: 0px 64px; + + width: 50%; + height: max-content; +} + +#profile-achievements > .page-subheader-text:hover { + color: var(--accent-value4); +} + +.profile-achievement-entry { + overflow: hidden; + + margin-top: 16px; + + height: 64px; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + border: 3px solid var(--distinction); + border-radius: 12px; +} + +.profile-achievement-entry.completed { + border: 3px solid var(--accent-value3); + + background-color: var(--accent-value2); +} + +.profile-achievement-entry-left { + height: 100%; + + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; +} + +.profile-achievement-entry-right { + height: 100%; + + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; +} + +.profile-achievement-entry-icon { + height: 64px; +} + +.profile-achievement-entry-name { + margin: 0; + padding: 0px 16px; + + color: var(--foreground); + + font-size: 24px; +} + +.profile-achievement-completed { + display: none; + + margin: 0; + padding: 0px 16px; + + color: var(--foreground); + + font-size: 24px; +} + +.profile-achievement-entry.completed .profile-achievement-completed { + display: block; +} + diff --git a/frontend/webpage/templates/achievements_page.html.template b/frontend/webpage/templates/achievements_page.html.template new file mode 100644 index 0000000..b4fb2a8 --- /dev/null +++ b/frontend/webpage/templates/achievements_page.html.template @@ -0,0 +1,40 @@ + +
+
+

Filters

+
+
+
+

From Games Owned

+
+
+
+

In Progress

+
+
+
+

Completed

+
+
+
+
+
+

+

Name

+

Description

+

Stages

+
+ +
+
+
diff --git a/frontend/webpage/templates/games_page.html.template b/frontend/webpage/templates/games_page.html.template new file mode 100644 index 0000000..1977414 --- /dev/null +++ b/frontend/webpage/templates/games_page.html.template @@ -0,0 +1,53 @@ + +
+
+

Filters

+
+
+
+

Games Owned

+
+
+
+
+
+

+

Name

+

Description

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+ Achievement Icon.png +

Latin

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+
+
\ No newline at end of file diff --git a/frontend/webpage/templates/profile_page.html.template b/frontend/webpage/templates/profile_page.html.template new file mode 100644 index 0000000..d2ce6a5 --- /dev/null +++ b/frontend/webpage/templates/profile_page.html.template @@ -0,0 +1,135 @@ +
+
+ User's Profile Pictuer +

Jane Doe

+
+
+

Platforms

+
+
+
+ Steam Logo +

Steam

+
+
+

Connected

+
+
+
+
+ Xbox Logo +

Xbox Live

+
+
+

Connected

+
+
+
+
+ PSN Logo +

PSN

+
+
+

Connected

+
+
+
+
+
+
+

Games

+
+
+ Some Game Icon +

Latin

+
+
+ Some Game Icon +

Latin

+
+
+ Some Game Icon +

Latin

+
+
+ Some Game Icon +

Latin

+
+
+
+

Achievements

+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
+ Some Achievement Icon +

Lorem Ipsum

+
+
+

Completed

+
+
+
+
\ No newline at end of file diff --git a/frontend/webpage/user.html b/frontend/webpage/user.html deleted file mode 100644 index 8d3caa8..0000000 --- a/frontend/webpage/user.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - Achievements Project - - - - - - - -
-
-
- -
-
-

Contemplating...

- Loading Symbol -
- -
-
- - - - - \ No newline at end of file diff --git a/sql/AuthProcs.sql b/sql/AuthProcs.sql deleted file mode 100644 index c8717ac..0000000 Binary files a/sql/AuthProcs.sql and /dev/null differ diff --git a/sql/Tables.sql b/sql/CreateTables.sql similarity index 83% rename from sql/Tables.sql rename to sql/CreateTables.sql index 323bab7..beb45bb 100644 --- a/sql/Tables.sql +++ b/sql/CreateTables.sql @@ -27,37 +27,24 @@ ----------------------------- -CREATE TYPE ImageType FROM VARCHAR(4) NULL -GO - CREATE TABLE [User] ( ID INT IDENTITY(0, 1) NOT NULL, Email VARCHAR(254) NOT NULL, Username VARCHAR(32) NOT NULL, - [Password] CHAR(64) NOT NULL, - [Salt] CHAR(32) NOT NULL, - Hue INT NOT NULL - CONSTRAINT HueDefault DEFAULT 0 - CONSTRAINT HueConstraint CHECK (0 <= Hue AND Hue <= 360), - ProfileImage ImageType, - [Admin] BIT NOT NULL - CONSTRAINT AdmivDefault DEFAULT 0, - Verified BIT NOT NULL - CONSTRAINT VerifiedDefault DEFAULT 0 + [Password] CHAR(256) NOT NULL PRIMARY KEY(ID) ) CREATE TABLE [Platform] ( ID INT IDENTITY(0, 1) NOT NULL, - PlatformName VARCHAR(32) NOT NULL, - Icon ImageType + PlatformName VARCHAR(32) NOT NULL PRIMARY KEY(ID) ) CREATE TABLE [Game] ( ID INT IDENTITY(0, 1) NOT NULL, Name VARCHAR(32) NOT NULL, - Icon ImageType + Thumbnail VARCHAR(256) NULL PRIMARY KEY(ID) ) @@ -67,7 +54,7 @@ CREATE TABLE [Achievement] ( Name VARCHAR(128) NOT NULL, Description VARCHAR(512) NULL, Stages INT NOT NULL, - Icon ImageType + Thumbnail VARCHAR(256) NULL PRIMARY KEY(ID) FOREIGN KEY(GameID) REFERENCES [Game](ID) ON UPDATE CASCADE @@ -95,7 +82,7 @@ CREATE TABLE [Progress] ( PlatformID INT NOT NULL, AchievementID INT NOT NULL, Progress INT NOT NULL - PRIMARY KEY(UserID, PlatformID, AchievementID) + PRIMARY KEY(UserID, AchievementID) FOREIGN KEY(UserID) REFERENCES [User](ID) ON UPDATE CASCADE ON DELETE CASCADE, @@ -109,8 +96,7 @@ CREATE TABLE [Progress] ( CREATE TABLE [IsOn] ( UserID INT NOT NULL, - PlatformID INT NOT NULL, - PlatformUserID VARCHAR(32) NOT NULL + PlatformID INT NOT NULL PRIMARY KEY(UserID, PlatformID) FOREIGN KEY(UserID) REFERENCES [User](ID) ON UPDATE CASCADE @@ -123,7 +109,7 @@ CREATE TABLE [IsOn] ( CREATE TABLE [ExistsOn] ( GameID INT NOT NULL, PlatformID INT NOT NULL, - PlatformGameID VARCHAR(32) NOT NULL + PlatformGameID INT NOT NULL PRIMARY KEY(GameID, PlatformID) FOREIGN KEY(GameID) REFERENCES [Game](ID) ON UPDATE CASCADE diff --git a/sql/CreateUserSP.sql b/sql/CreateUserSP.sql new file mode 100644 index 0000000..0af4edf Binary files /dev/null and b/sql/CreateUserSP.sql differ diff --git a/sql/DataProcs.sql b/sql/DataProcs.sql deleted file mode 100644 index a3d026f..0000000 --- a/sql/DataProcs.sql +++ /dev/null @@ -1,866 +0,0 @@ -DELETE FROM [User] -DELETE FROM [Game] -DELETE FROM [Platform] -GO - --------------- --- HAS USER -- --------------- - -CREATE PROCEDURE HasUser( - @result BIT OUTPUT -) -AS -SET @result = CASE WHEN EXISTS (SELECT * FROM [User]) THEN 1 ELSE 0 END -RETURN 0 -GO - -------------- --- OP USER -- -------------- - -CREATE PROCEDURE OpUser( - @userId INT -) -AS -UPDATE [User] SET Admin = 1 WHERE @userId = [User].ID -RETURN 0 -GO - ------------------------ --- GET ID FROM EMAIL -- ------------------------ - -CREATE PROCEDURE GetIdFromEmail( - @email VARCHAR(254), - @userId INT OUTPUT -) -AS -SELECT @userId = ID -FROM [User] -WHERE Email = @email -RETURN 0 -GO - ---------------------------------------- --- 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, 'png') -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 - ------------------------------------------- --- GET NOTEWORTHY ACHIEVEMENTS FOR USER -- ------------------------------------------- - -CREATE PROCEDURE GetNoteworthyAchievementsForUser ( - @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 TOP 5 Achievement.ID, Name, Completion -FROM Achievement -JOIN MaxProgress ON Achievement.ID = MaxProgress.AchievementID -JOIN AchievementCompletion AC ON AC.ID = Achievement.ID -WHERE UserID = @userId AND Progress = Stages -ORDER BY Completion ASC, NumberUsers DESC -RETURN 0 -GO - ---------------------- --- GET ACHIEVEMENT -- ---------------------- - -CREATE PROCEDURE GetAchievement ( - @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 Achievement.ID, Name, Completion, Description, Difficulty, Quality -FROM Achievement -LEFT JOIN AchievementCompletion AC ON Achievement.ID = AC.ID -LEFT JOIN AchievementRatings AR ON Achievement.ID = AR.ID -WHERE Achievement.ID = @achievementId -RETURN 0 -GO - ---------------------------------- --- GET RATINGS FOR ACHIEVEMENT -- ---------------------------------- - -CREATE PROCEDURE GetRatingsForAchievement( - @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 UserID, [Username], Difficulty, Quality, [Description] -FROM Rating -JOIN [User] ON [User].ID = Rating.UserID -WHERE AchievementID = @achievementId -RETURN 0 -GO - -------------------------- --- GET RATINGS BY USER -- -------------------------- - -CREATE PROCEDURE GetRatingsByUser( - @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 AchievementID, Achievement.[Name], Difficulty, Quality, Rating.[Description] -FROM Rating -JOIN Achievement ON Achievement.ID = Rating.UserID -WHERE UserID = @userId -RETURN 0 -GO - -EXEC GetRatingsByUser 0 - ------------------- --- HAS PROGRESS -- ------------------- - -CREATE PROCEDURE HasProgress ( - @userId INT, - @achievementId INT, - @result BIT OUTPUT -) -AS -IF NOT @userId IN (SELECT ID FROM [User]) -BEGIN - PRINT 'No user with the specified ID was found' - RETURN 1 -END -IF NOT @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -SET @result = CASE WHEN EXISTS (SELECT * FROM Progress WHERE UserID = @userId AND AchievementID = @achievementId) THEN 1 ELSE 0 END -RETURN 0 -GO - ----------------- --- GET RATING -- ----------------- - -CREATE PROCEDURE GetRating( - @userId INT, - @achievementId 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 @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -SELECT Difficulty, Quality, [Description] -FROM Rating -WHERE UserID = @userId AND AchievementID = @achievementId -RETURN 0 -GO - ----------------- --- SET RATING -- ----------------- - -CREATE PROCEDURE SetRating( - @userId INT, - @achievementId INT, - @difficulty FLOAT, - @quality FLOAT, - @review VARCHAR(1024) -) -AS -IF NOT @userId IN (SELECT ID FROM [User]) -BEGIN - PRINT 'No user with the specified ID was found' - RETURN 1 -END -IF NOT @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -IF NOT EXISTS (SELECT * FROM Progress WHERE UserID = @userId AND AchievementID = @achievementId) -BEGIN - PRINT 'User does not have progress on achievement' - RETURN 3 -END -IF @difficulty < 0 OR @difficulty > 10 -BEGIN - PRINT 'Difficult must be between 0 and 10' - RETURN 4 -END -IF @quality < 0 OR @quality > 10 -BEGIN - PRINT 'Quality must be between 0 and 10' - RETURN 5 -END -IF @quality IS NULL AND @quality IS NULL AND @review IS NULL - DELETE FROM Rating WHERE UserID = @userId AND AchievementID = @achievementId -ELSE IF EXISTS (SELECT * FROM Rating WHERE UserID = @userId AND AchievementID = @achievementId) - UPDATE Rating SET - Quality = @quality, - Difficulty = @difficulty, - [Description] = @review - WHERE UserID = @userId AND AchievementID = @achievementId -ELSE - INSERT INTO Rating VALUES (@userId, @achievementId, @quality, @difficulty, @review) -RETURN 0 -GO diff --git a/sql/Indexes.sql b/sql/Indexes.sql deleted file mode 100644 index 9723578..0000000 --- a/sql/Indexes.sql +++ /dev/null @@ -1,21 +0,0 @@ ------------------ --- Email Index -- ------------------ - -CREATE NONCLUSTERED INDEX EmailIndex ON [User](Email) - ------------------- --- Name Indexes -- ------------------- - -CREATE NONCLUSTERED INDEX UsernameIndex ON [User](Username) -CREATE NONCLUSTERED INDEX GameNameIndex ON Game(Name) -CREATE NONCLUSTERED INDEX AchievementNameIndex ON Achievement(Name) - --------------------- --- Rating Indexes -- --------------------- - -CREATE NONCLUSTERED INDEX DifficultyIndex ON Rating(Difficulty) -CREATE NONCLUSTERED INDEX QualityIndex ON Rating(Quality) - diff --git a/sql/Mondo.sql b/sql/Mondo.sql deleted file mode 100644 index e23f8aa..0000000 --- a/sql/Mondo.sql +++ /dev/null @@ -1,1339 +0,0 @@ - --------------------- - --------------------- - -- -- ------- CREATE TABLES ------ - -- -- - --------------------- - --------------------- - -CREATE TYPE ImageType FROM VARCHAR(4) NULL -GO - -CREATE TABLE [User] ( - ID INT IDENTITY(0, 1) NOT NULL, - Email VARCHAR(254) NOT NULL, - Username VARCHAR(32) NOT NULL, - [Password] CHAR(64) NOT NULL, - [Salt] CHAR(32) NOT NULL, - Hue INT NOT NULL - CONSTRAINT HueDefault DEFAULT 0 - CONSTRAINT HueConstraint CHECK (0 <= Hue AND Hue <= 360), - ProfileImage ImageType, - [Admin] BIT NOT NULL - CONSTRAINT AdmivDefault DEFAULT 0, - Verified BIT NOT NULL - CONSTRAINT VerifiedDefault DEFAULT 0 - PRIMARY KEY(ID) -) - -CREATE TABLE [Platform] ( - ID INT IDENTITY(0, 1) 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, - Icon ImageType - PRIMARY KEY(ID) -) - -CREATE TABLE [Achievement] ( - ID INT IDENTITY(0, 1) NOT NULL, - GameID INT NOT NULL, - Name VARCHAR(128) NOT NULL, - Description VARCHAR(512) NULL, - Stages INT NOT NULL, - Icon ImageType - PRIMARY KEY(ID) - FOREIGN KEY(GameID) REFERENCES [Game](ID) - ON UPDATE CASCADE - ON DELETE CASCADE -) - -CREATE TABLE [Owns] ( - UserID INT NOT NULL, - GameID INT NOT NULL, - PlatformID INT NOT NULL - PRIMARY KEY(UserID, GameID, PlatformID) - FOREIGN KEY(UserID) REFERENCES [User](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(GameID) REFERENCES [Game](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(PlatformID) REFERENCES [Platform](ID) - ON UPDATE CASCADE - ON DELETE CASCADE -) - -CREATE TABLE [Progress] ( - UserID INT NOT NULL, - PlatformID INT NOT NULL, - AchievementID INT NOT NULL, - Progress INT NOT NULL - PRIMARY KEY(UserID, PlatformID, AchievementID) - FOREIGN KEY(UserID) REFERENCES [User](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(PlatformID) REFERENCES [Platform](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(AchievementID) REFERENCES [Achievement](ID) - ON UPDATE CASCADE - ON DELETE CASCADE -) - -CREATE TABLE [IsOn] ( - UserID INT NOT NULL, - PlatformID INT NOT NULL, - PlatformUserID VARCHAR(32) NOT NULL - PRIMARY KEY(UserID, PlatformID) - FOREIGN KEY(UserID) REFERENCES [User](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(PlatformID) REFERENCES [Platform](ID) - ON UPDATE CASCADE - ON DELETE CASCADE -) - -CREATE TABLE [ExistsOn] ( - GameID INT NOT NULL, - PlatformID INT NOT NULL, - PlatformGameID VARCHAR(32) NOT NULL - PRIMARY KEY(GameID, PlatformID) - FOREIGN KEY(GameID) REFERENCES [Game](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(PlatformID) REFERENCES [Platform](ID) - ON UPDATE CASCADE -) - -CREATE TABLE [Rating] ( - UserID INT NOT NULL, - AchievementID INT NOT NULL, - Quality FLOAT NULL, - Difficulty FLOAT NULL, - [Description] VARCHAR(1024) NULL - PRIMARY KEY(UserID, AchievementID) - FOREIGN KEY(UserID) REFERENCES [User](ID) - ON UPDATE CASCADE - ON DELETE CASCADE, - FOREIGN KEY(AchievementID) REFERENCES [Achievement](ID) - ON UPDATE CASCADE - ON DELETE CASCADE -) - - -------------------- - -------------------- - -- -- ------- CREATE VIEWS ------ - -- -- - -------------------- - -------------------- - --- 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, COUNT(UserID) AS NumberUsers - 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 - --- List of games owned by a user removing duplicate ownership if owned on multiple platforms -CREATE VIEW OwnsUnique -AS - SELECT UserID, GameID - FROM Owns - GROUP BY UserID, GameID -GO - - ---------------------- - ---------------------- - -- -- ------- CREATE INDICES ------ - -- -- - ---------------------- - ---------------------- - ------------------ --- Email Index -- ------------------ - -CREATE NONCLUSTERED INDEX EmailIndex ON [User](Email) - ------------------- --- Name Indexes -- ------------------- - -CREATE NONCLUSTERED INDEX UsernameIndex ON [User](Username) -CREATE NONCLUSTERED INDEX GameNameIndex ON Game(Name) -CREATE NONCLUSTERED INDEX AchievementNameIndex ON Achievement(Name) - --------------------- --- Rating Indexes -- --------------------- - -CREATE NONCLUSTERED INDEX DifficultyIndex ON Rating(Difficulty) -CREATE NONCLUSTERED INDEX QualityIndex ON Rating(Quality) - - ------------------------- - ------------------------- - -- -- ------- CREATE PROCEDURES ------ - -- -- - ------------------------- - ------------------------- - ------------------------ --- CREATE A NEW USER -- ------------------------ - -CREATE PROCEDURE [CreateUser] ( - @email VARCHAR(254), - @username VARCHAR(32), - @salt CHAR(32), - @password CHAR(64), - @ID INT OUTPUT, - @Hue INT OUTPUT -) AS -IF EXISTS (SELECT Email FROM [User] WHERE Email = @email) -BEGIN - PRINT 'Email is already registered with an account.' - RETURN 1 -END -IF @username IS NULL -BEGIN - PRINT 'Username cannot be null' - RETURN 2 -END -IF @password IS NULL -BEGIN - PRINT 'Password cannot be null' - RETURN 3 -END -IF @salt IS NULL -BEGIN - PRINT 'Password salt cannot be null' - RETURN 4 -END - -INSERT INTO [User](Email, Username, Salt, [Password]) VALUES (@email, @username, @salt, @password) -SET @ID = @@IDENTITY -SELECT @Hue = Hue FROM [User] WHERE ID = @ID - -RETURN 0 -GO - -------------------------- --- GET USER LOGIN INFO -- -------------------------- - -CREATE PROCEDURE GetUserLogin( - @email VARCHAR(254) -) AS -IF NOT @email IN (SELECT Email FROM [User]) -BEGIN - PRINT 'No user exists with specified email' - RETURN 1 -END -SELECT Id, Salt, [Password], Hue, [Admin] FROM [User] WHERE Email = @email -RETURN 0 -GO - -DELETE FROM [User] -DELETE FROM [Game] -DELETE FROM [Platform] -GO - --------------- --- HAS USER -- --------------- - -CREATE PROCEDURE HasUser( - @result BIT OUTPUT -) -AS -SET @result = CASE WHEN EXISTS (SELECT * FROM [User]) THEN 1 ELSE 0 END -RETURN 0 -GO - -------------- --- OP USER -- -------------- - -CREATE PROCEDURE OpUser( - @userId INT -) -AS -UPDATE [User] SET Admin = 1 WHERE @userId = [User].ID -RETURN 0 -GO - ------------------------ --- GET ID FROM EMAIL -- ------------------------ - -CREATE PROCEDURE GetIdFromEmail( - @email VARCHAR(254), - @userId INT OUTPUT -) -AS -SELECT @userId = ID -FROM [User] -WHERE Email = @email -RETURN 0 -GO - ---------------------------------------- --- 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, 'png') -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 - ------------------------------------------- --- GET NOTEWORTHY ACHIEVEMENTS FOR USER -- ------------------------------------------- - -CREATE PROCEDURE GetNoteworthyAchievementsForUser ( - @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 TOP 5 Achievement.ID, Name, Completion -FROM Achievement -JOIN MaxProgress ON Achievement.ID = MaxProgress.AchievementID -JOIN AchievementCompletion AC ON AC.ID = Achievement.ID -WHERE UserID = @userId AND Progress = Stages -ORDER BY Completion ASC, NumberUsers DESC -RETURN 0 -GO - ---------------------- --- GET ACHIEVEMENT -- ---------------------- - -CREATE PROCEDURE GetAchievement ( - @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 Achievement.ID, Name, Completion, Description, Difficulty, Quality -FROM Achievement -LEFT JOIN AchievementCompletion AC ON Achievement.ID = AC.ID -LEFT JOIN AchievementRatings AR ON Achievement.ID = AR.ID -WHERE Achievement.ID = @achievementId -RETURN 0 -GO - ---------------------------------- --- GET RATINGS FOR ACHIEVEMENT -- ---------------------------------- - -CREATE PROCEDURE GetRatingsForAchievement( - @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 UserID, [Username], Difficulty, Quality, [Description] -FROM Rating -JOIN [User] ON [User].ID = Rating.UserID -WHERE AchievementID = @achievementId -RETURN 0 -GO - -------------------------- --- GET RATINGS BY USER -- -------------------------- - -CREATE PROCEDURE GetRatingsByUser( - @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 AchievementID, Achievement.[Name], Difficulty, Quality, Rating.[Description] -FROM Rating -JOIN Achievement ON Achievement.ID = Rating.UserID -WHERE UserID = @userId -RETURN 0 -GO - -EXEC GetRatingsByUser 0 - ------------------- --- HAS PROGRESS -- ------------------- - -CREATE PROCEDURE HasProgress ( - @userId INT, - @achievementId INT, - @result BIT OUTPUT -) -AS -IF NOT @userId IN (SELECT ID FROM [User]) -BEGIN - PRINT 'No user with the specified ID was found' - RETURN 1 -END -IF NOT @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -SET @result = CASE WHEN EXISTS (SELECT * FROM Progress WHERE UserID = @userId AND AchievementID = @achievementId) THEN 1 ELSE 0 END -RETURN 0 -GO - ----------------- --- GET RATING -- ----------------- - -CREATE PROCEDURE GetRating( - @userId INT, - @achievementId 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 @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -SELECT Difficulty, Quality, [Description] -FROM Rating -WHERE UserID = @userId AND AchievementID = @achievementId -RETURN 0 -GO - ----------------- --- SET RATING -- ----------------- - -CREATE PROCEDURE SetRating( - @userId INT, - @achievementId INT, - @difficulty FLOAT, - @quality FLOAT, - @review VARCHAR(1024) -) -AS -IF NOT @userId IN (SELECT ID FROM [User]) -BEGIN - PRINT 'No user with the specified ID was found' - RETURN 1 -END -IF NOT @achievementId IN (SELECT ID FROM Achievement) -BEGIN - PRINT 'No achievement with the specified ID was found' - RETURN 2 -END -IF NOT EXISTS (SELECT * FROM Progress WHERE UserID = @userId AND AchievementID = @achievementId) -BEGIN - PRINT 'User does not have progress on achievement' - RETURN 3 -END -IF @difficulty < 0 OR @difficulty > 10 -BEGIN - PRINT 'Difficult must be between 0 and 10' - RETURN 4 -END -IF @quality < 0 OR @quality > 10 -BEGIN - PRINT 'Quality must be between 0 and 10' - RETURN 5 -END -IF @quality IS NULL AND @quality IS NULL AND @review IS NULL - DELETE FROM Rating WHERE UserID = @userId AND AchievementID = @achievementId -ELSE IF EXISTS (SELECT * FROM Rating WHERE UserID = @userId AND AchievementID = @achievementId) - UPDATE Rating SET - Quality = @quality, - Difficulty = @difficulty, - [Description] = @review - WHERE UserID = @userId AND AchievementID = @achievementId -ELSE - INSERT INTO Rating VALUES (@userId, @achievementId, @quality, @difficulty, @review) -RETURN 0 -GO - -------------------------- --- SEARCH ACHIEVEMENTS -- -------------------------- - -CREATE PROCEDURE SearchAchievements( - @searchTerm VARCHAR(32), - @userId INT, - @completed BIT, - @minCompletion FLOAT, - @maxCompletion FLOAT, - @minDifficulty FLOAT, - @maxDifficulty FLOAT, - @minQuality FLOAT, - @maxQuality FLOAT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS -IF @userId IS NULL AND @completed = 1 -BEGIN - PRINT 'Cannot search for completed achievements with no user specified' - RETURN 1 -END - -IF @completed IS NULL -SET @completed = 0 - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 Achievement.ID, Game.[Name] AS Game, Achievement.[Name], Completion, Difficulty, Quality -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 (@completed <> 1 OR Achievement.ID IN (SELECT AchievementID FROM MaxProgress WHERE UserID = @userId AND 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 ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END DESC -RETURN 0 -GO - ------------------- --- SEARCH USERS -- ------------------- - -CREATE PROCEDURE SearchUsers( - @searchTerm VARCHAR(32), - @minOwned INT, - @maxOwned INT, - @minCompleted INT, - @maxCompleted INT, - @minAvgCompletion INT, - @maxAvgCompletion INT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 * -FROM ( - SELECT [User].ID, Username, ISNULL(GameCount, 0) AS GameCount, ISNULL(AchievementCount, 0) AS AchievementCount, AvgCompletion, ISNULL(PerfectGames, 0) AS PerfectGames - FROM [User] - LEFT JOIN ( - SELECT - UserID, - COUNT(GameID) AS GameCount, - SUM(Completed) AS AchievementCount, - AVG((Completed * 100) / Total) AS AvgCompletion, - SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS PerfectGames - FROM GameCompletionByUser - GROUP BY UserID - ) AS Completion ON Completion.UserID = [User].ID -) AS Results -WHERE (Username LIKE @searchTerm) - AND (@minOwned IS NULL OR @minOwned <= GameCount ) - AND (@maxOwned IS NULL OR @maxOwned >= GameCount ) - AND (@minCompleted IS NULL OR @minCompleted <= AchievementCount) - AND (@maxCompleted IS NULL OR @maxCompleted >= AchievementCount) - AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion ) - AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Username' THEN Username ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Username' THEN Username ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END DESC -RETURN 0 -GO ------------------- --- SEARCH GAMES -- ------------------- - -CREATE PROCEDURE SearchGames( - @searchTerm VARCHAR(32), - @userId INT, - @owned BIT, - @minAvgCompletion INT, - @maxAvgCompletion INT, - @minNumOwners INT, - @maxNumOwners INT, - @minNumPerfects INT, - @maxNumPerfects INT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS -IF @userId IS NULL AND @owned = 1 -BEGIN - PRINT 'Cannot search for owned games with no user specified' - RETURN 1 -END - -PRINT 'UserID, Owned' -PRINT @userId -PRINT @owned - -IF @owned IS NULL -SET @owned = 0 - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 * -FROM ( - SELECT - Game.ID, - [Name], - AchievementCount, - AvgCompletion, - ISNULL(NumOwners, 0) AS NumOwners, - ISNULL(NumPerfects, 0) AS NumPerfects - FROM Game - LEFT JOIN ( - SELECT - GameID, - Total AS AchievementCount, - AVG((Completed * 100) / Total) AS AvgCompletion, - SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS NumPerfects - FROM GameCompletionByUser - GROUP BY GameID, Total - ) AS Completion ON Completion.GameID = Game.ID - LEFT JOIN ( - SELECT GameID, COUNT(UserID) AS NumOwners - FROM OwnsUnique - GROUP BY GameID - ) AS Owners ON Owners.GameID = Game.ID -) AS Results -WHERE ([Name] LIKE @searchTerm) - AND (@owned <> 1 OR ID IN (SELECT GameID FROM OwnsUnique WHERE UserID = @userId)) - AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion) - AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion) - AND (@minNumOwners IS NULL OR @minNumOwners <= NumOwners ) - AND (@maxNumOwners IS NULL OR @maxNumOwners >= NumOwners ) - AND (@minNumPerfects IS NULL OR @minNumPerfects <= NumPerfects ) - AND (@maxNumPerfects IS NULL OR @maxNumPerfects >= NumPerfects ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END DESC -RETURN 0 -GO - -EXEC SearchGames '', 3, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL diff --git a/sql/SearchProcs.sql b/sql/SearchProcs.sql deleted file mode 100644 index abe3a03..0000000 --- a/sql/SearchProcs.sql +++ /dev/null @@ -1,200 +0,0 @@ -------------------------- --- SEARCH ACHIEVEMENTS -- -------------------------- - -CREATE PROCEDURE SearchAchievements( - @searchTerm VARCHAR(32), - @userId INT, - @completed BIT, - @minCompletion FLOAT, - @maxCompletion FLOAT, - @minDifficulty FLOAT, - @maxDifficulty FLOAT, - @minQuality FLOAT, - @maxQuality FLOAT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS -IF @userId IS NULL AND @completed = 1 -BEGIN - PRINT 'Cannot search for completed achievements with no user specified' - RETURN 1 -END - -IF @completed IS NULL -SET @completed = 0 - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 Achievement.ID, Game.[Name] AS Game, Achievement.[Name], Completion, Difficulty, Quality -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 (@completed <> 1 OR Achievement.ID IN (SELECT AchievementID FROM MaxProgress WHERE UserID = @userId AND 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 ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END DESC -RETURN 0 -GO - ------------------- --- SEARCH USERS -- ------------------- - -CREATE PROCEDURE SearchUsers( - @searchTerm VARCHAR(32), - @minOwned INT, - @maxOwned INT, - @minCompleted INT, - @maxCompleted INT, - @minAvgCompletion INT, - @maxAvgCompletion INT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 * -FROM ( - SELECT [User].ID, Username, ISNULL(GameCount, 0) AS GameCount, ISNULL(AchievementCount, 0) AS AchievementCount, AvgCompletion, ISNULL(PerfectGames, 0) AS PerfectGames - FROM [User] - LEFT JOIN ( - SELECT - UserID, - COUNT(GameID) AS GameCount, - SUM(Completed) AS AchievementCount, - AVG((Completed * 100) / Total) AS AvgCompletion, - SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS PerfectGames - FROM GameCompletionByUser - GROUP BY UserID - ) AS Completion ON Completion.UserID = [User].ID -) AS Results -WHERE (Username LIKE @searchTerm) - AND (@minOwned IS NULL OR @minOwned <= GameCount ) - AND (@maxOwned IS NULL OR @maxOwned >= GameCount ) - AND (@minCompleted IS NULL OR @minCompleted <= AchievementCount) - AND (@maxCompleted IS NULL OR @maxCompleted >= AchievementCount) - AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion ) - AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Username' THEN Username ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Username' THEN Username ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END DESC -RETURN 0 -GO ------------------- --- SEARCH GAMES -- ------------------- - -CREATE PROCEDURE SearchGames( - @searchTerm VARCHAR(32), - @userId INT, - @owned BIT, - @minAvgCompletion INT, - @maxAvgCompletion INT, - @minNumOwners INT, - @maxNumOwners INT, - @minNumPerfects INT, - @maxNumPerfects INT, - @orderBy VARCHAR(16), - @orderDirection VARCHAR(4) -) -AS -IF @userId IS NULL AND @owned = 1 -BEGIN - PRINT 'Cannot search for owned games with no user specified' - RETURN 1 -END - -PRINT 'UserID, Owned' -PRINT @userId -PRINT @owned - -IF @owned IS NULL -SET @owned = 0 - -IF @searchTerm IS NULL OR @searchTerm = '' - SET @searchTerm = '%' -ELSE - SET @searchTerm = '%' + @searchTerm + '%' - -SELECT TOP 100 * -FROM ( - SELECT - Game.ID, - [Name], - AchievementCount, - AvgCompletion, - ISNULL(NumOwners, 0) AS NumOwners, - ISNULL(NumPerfects, 0) AS NumPerfects - FROM Game - LEFT JOIN ( - SELECT - GameID, - Total AS AchievementCount, - AVG((Completed * 100) / Total) AS AvgCompletion, - SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS NumPerfects - FROM GameCompletionByUser - GROUP BY GameID, Total - ) AS Completion ON Completion.GameID = Game.ID - LEFT JOIN ( - SELECT GameID, COUNT(UserID) AS NumOwners - FROM OwnsUnique - GROUP BY GameID - ) AS Owners ON Owners.GameID = Game.ID -) AS Results -WHERE ([Name] LIKE @searchTerm) - AND (@owned <> 1 OR ID IN (SELECT GameID FROM OwnsUnique WHERE UserID = @userId)) - AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion) - AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion) - AND (@minNumOwners IS NULL OR @minNumOwners <= NumOwners ) - AND (@maxNumOwners IS NULL OR @maxNumOwners >= NumOwners ) - AND (@minNumPerfects IS NULL OR @minNumPerfects <= NumPerfects ) - AND (@maxNumPerfects IS NULL OR @maxNumPerfects >= NumPerfects ) -ORDER BY - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END ASC, - CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END ASC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END DESC, - CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END DESC -RETURN 0 -GO - -EXEC SearchGames '', 3, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL diff --git a/sql/Views.sql b/sql/Views.sql deleted file mode 100644 index 17870ab..0000000 --- a/sql/Views.sql +++ /dev/null @@ -1,42 +0,0 @@ --- 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, COUNT(UserID) AS NumberUsers - 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 - --- List of games owned by a user removing duplicate ownership if owned on multiple platforms -CREATE VIEW OwnsUnique -AS - SELECT UserID, GameID - FROM Owns - GROUP BY UserID, GameID -GO