Compare commits
10 commits
9ba8a99e82
...
a9f44c29af
Author | SHA1 | Date | |
---|---|---|---|
a9f44c29af | |||
a8cf583569 | |||
4df0a804b3 | |||
627cc810ed | |||
b229ff9a15 | |||
052052d76b | |||
40a0e4046a | |||
13736d0ef9 | |||
9e2ef9cfdc | |||
5a1dd33dfe |
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
tmp/*
|
||||
sql/CreateBackendUser.sql
|
3
backend/.gitignore
vendored
|
@ -22,3 +22,6 @@ src/main/resources/application-local.properties
|
|||
|
||||
# Server Keystore
|
||||
src/main/resources/achievements-ssl-key.p12
|
||||
|
||||
# Program Data
|
||||
storage/
|
|
@ -1,20 +1,27 @@
|
|||
package achievements;
|
||||
|
||||
import achievements.services.DbConnectionService;
|
||||
import achievements.misc.DbConnection;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@SpringBootApplication
|
||||
import java.time.Duration;
|
||||
|
||||
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
|
||||
@EnableScheduling
|
||||
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(DbConnectionService.class);
|
||||
var db = context.getBean(DbConnection.class);
|
||||
if (db.getConnection() == null) {
|
||||
SpringApplication.exit(context, () -> 0);
|
||||
}
|
||||
|
@ -25,10 +32,15 @@ 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();
|
||||
}
|
||||
}
|
17
backend/src/main/java/achievements/apis/PlatformAPI.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package achievements.apis;
|
||||
|
||||
import achievements.data.APIResponse;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
public abstract class PlatformAPI {
|
||||
|
||||
protected int id;
|
||||
protected RestTemplate rest;
|
||||
|
||||
protected PlatformAPI(int id, RestTemplate rest) {
|
||||
this.id = id;
|
||||
this.rest = rest;
|
||||
}
|
||||
|
||||
public abstract APIResponse get(String userId);
|
||||
}
|
113
backend/src/main/java/achievements/apis/SteamAPI.java
Normal file
|
@ -0,0 +1,113 @@
|
|||
package achievements.apis;
|
||||
|
||||
import achievements.apis.steam.GetOwnedGameBody;
|
||||
import achievements.apis.steam.GetPlayerAchievementsBody;
|
||||
import achievements.apis.steam.GetSchemaForGameBody;
|
||||
import achievements.data.APIResponse;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
public class SteamAPI extends PlatformAPI {
|
||||
|
||||
private String apiKey;
|
||||
|
||||
public SteamAPI(int id, RestTemplate rest) {
|
||||
super(id, rest);
|
||||
try {
|
||||
var file = new FileInputStream("storage/apis/" + id + ".properties");
|
||||
var properties = new Properties();
|
||||
properties.load(file);
|
||||
|
||||
apiKey = properties.getProperty("api-key");
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse get(String userId) {
|
||||
var headers = new HttpHeaders();
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
var entity = new HttpEntity<String>(headers);
|
||||
var ownedGamesUrl = UriComponentsBuilder.fromHttpUrl("http://api.steampowered.com/IPlayerService/GetOwnedGames/v1/")
|
||||
.queryParam("key", apiKey)
|
||||
.queryParam("steamid", userId)
|
||||
.queryParam("include_appinfo", true)
|
||||
.queryParam("include_played_free_games", true)
|
||||
.toUriString();
|
||||
|
||||
var gameSchemaBaseUrl = UriComponentsBuilder.fromHttpUrl("https://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/")
|
||||
.queryParam("key", apiKey);
|
||||
var playerAchievementsBaseUrl = UriComponentsBuilder.fromHttpUrl("https://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v1/")
|
||||
.queryParam("key", apiKey)
|
||||
.queryParam("steamid", userId);
|
||||
|
||||
var games = new ArrayList<APIResponse.Game>();
|
||||
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<String, APIResponse.Game.Achievement>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package achievements.apis.steam;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetOwnedGameBody {
|
||||
|
||||
public static class Response {
|
||||
|
||||
public static class Game {
|
||||
@JsonProperty("appid")
|
||||
private int appid;
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
@JsonProperty("playtime_forever")
|
||||
private int playtime_forever;
|
||||
@JsonProperty("img_icon_url")
|
||||
private String img_icon_url;
|
||||
@JsonProperty("img_logo_url")
|
||||
private String img_logo_url;
|
||||
|
||||
public int getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public void setAppid(int appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
|
||||
public int getPlaytime_forever() {
|
||||
return playtime_forever;
|
||||
}
|
||||
|
||||
public void setPlaytime_forever(int playtime_forever) {
|
||||
this.playtime_forever = playtime_forever;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getImg_icon_url() {
|
||||
return img_icon_url;
|
||||
}
|
||||
|
||||
public void setImg_icon_url(String img_icon_url) {
|
||||
this.img_icon_url = img_icon_url;
|
||||
}
|
||||
|
||||
public String getImg_logo_url() {
|
||||
return img_logo_url;
|
||||
}
|
||||
|
||||
public void setImg_logo_url(String img_logo_url) {
|
||||
this.img_logo_url = img_logo_url;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("game_count")
|
||||
private int game_count;
|
||||
@JsonProperty("games")
|
||||
private List<Game> games;
|
||||
|
||||
public int getGame_count() {
|
||||
return game_count;
|
||||
}
|
||||
|
||||
public void setGame_count(int game_count) {
|
||||
this.game_count = game_count;
|
||||
}
|
||||
|
||||
public List<Game> getGames() {
|
||||
return games;
|
||||
}
|
||||
|
||||
public void setGames(List<Game> games) {
|
||||
this.games = games;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("response")
|
||||
private Response response;
|
||||
|
||||
public Response getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(Response response) {
|
||||
this.response = response;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package achievements.apis.steam;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetPlayerAchievementsBody {
|
||||
public static class PlayerStats {
|
||||
public static class Achievement {
|
||||
@JsonProperty("apiname")
|
||||
private String apiname;
|
||||
@JsonProperty("achieved")
|
||||
private int achieved;
|
||||
|
||||
public String getApiname() {
|
||||
return apiname;
|
||||
}
|
||||
|
||||
public void setApiname(String apiname) {
|
||||
this.apiname = apiname;
|
||||
}
|
||||
|
||||
public int getAchieved() {
|
||||
return achieved;
|
||||
}
|
||||
|
||||
public void setAchieved(int achieved) {
|
||||
this.achieved = achieved;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("achievements")
|
||||
private List<Achievement> achievements;
|
||||
|
||||
public List<Achievement> getAchievements() {
|
||||
return achievements;
|
||||
}
|
||||
|
||||
public void setAchievements(List<Achievement> achievements) {
|
||||
this.achievements = achievements;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("playerstats")
|
||||
private PlayerStats playerstats;
|
||||
|
||||
public PlayerStats getPlayerstats() {
|
||||
return playerstats;
|
||||
}
|
||||
|
||||
public void setPlayerstats(PlayerStats playerstats) {
|
||||
this.playerstats = playerstats;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package achievements.apis.steam;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetSchemaForGameBody {
|
||||
public static class Game {
|
||||
public static class GameStats {
|
||||
public static class Achievement {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
@JsonProperty("displayName")
|
||||
private String displayName;
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
@JsonProperty("icon")
|
||||
private String icon;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("achievements")
|
||||
private List<Achievement> achievements;
|
||||
|
||||
public List<Achievement> getAchievements() {
|
||||
return achievements;
|
||||
}
|
||||
|
||||
public void setAchievements(List<Achievement> achievements) {
|
||||
this.achievements = achievements;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("availableGameStats")
|
||||
private GameStats availableGameStats;
|
||||
|
||||
public GameStats getAvailableGameStats() {
|
||||
return availableGameStats;
|
||||
}
|
||||
|
||||
public void setAvailableGameStats(GameStats availableGameStats) {
|
||||
this.availableGameStats = availableGameStats;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("game")
|
||||
private Game game;
|
||||
|
||||
public Game getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public void setGame(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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("{}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
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("{}");
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package achievements.controllers;
|
||||
|
||||
import achievements.services.ImageService;
|
||||
import achievements.services.GameService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/game")
|
||||
public class GameController {
|
||||
|
||||
@Autowired
|
||||
private GameService gameService;
|
||||
@Autowired
|
||||
private ImageService imageService;
|
||||
|
||||
@GetMapping(value = "/{game}/image")
|
||||
public void getProfilePicture(@PathVariable("game") int game, HttpServletResponse response) {
|
||||
var icon = gameService.getIcon(game);
|
||||
imageService.send(icon, "game", response);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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("{}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
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("[]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
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("{}");
|
||||
}
|
||||
}
|
||||
}
|
36
backend/src/main/java/achievements/data/APError.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
}
|
126
backend/src/main/java/achievements/data/APIResponse.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
package achievements.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class APIResponse {
|
||||
|
||||
public static class Game {
|
||||
|
||||
public static class Achievement {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
@JsonProperty("stages")
|
||||
private int stages;
|
||||
@JsonProperty("progress")
|
||||
private int progress;
|
||||
@JsonProperty("thumbnail")
|
||||
private String thumbnail;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getStages() {
|
||||
return stages;
|
||||
}
|
||||
|
||||
public void setStages(int stages) {
|
||||
this.stages = stages;
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public String getThumbnail() {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public void setThumbnail(String thumbnail) {
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("platformGameId")
|
||||
private String platformGameId;
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
@JsonProperty("played")
|
||||
private boolean played;
|
||||
@JsonProperty("thumbnail")
|
||||
private String thumbnail;
|
||||
@JsonProperty("achievements")
|
||||
private List<Achievement> achievements;
|
||||
|
||||
public String getPlatformGameId() {
|
||||
return platformGameId;
|
||||
}
|
||||
|
||||
public void setPlatformGameId(String platformGameId) {
|
||||
this.platformGameId = platformGameId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isPlayed() {
|
||||
return played;
|
||||
}
|
||||
|
||||
public void setPlayed(boolean played) {
|
||||
this.played = played;
|
||||
}
|
||||
|
||||
public String getThumbnail() {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public void setThumbnail(String thumbnail) {
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
|
||||
public List<Achievement> getAchievements() {
|
||||
return achievements;
|
||||
}
|
||||
|
||||
public void setAchievements(List<Achievement> achievements) {
|
||||
this.achievements = achievements;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("games")
|
||||
private List<Game> games;
|
||||
|
||||
public List<Game> getGames() {
|
||||
return games;
|
||||
}
|
||||
|
||||
public void setGames(List<Game> games) {
|
||||
this.games = games;
|
||||
}
|
||||
}
|
17
backend/src/main/java/achievements/data/APPostRequest.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
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<Achievement> achievements;
|
||||
|
||||
public Achievements() { achievements = new ArrayList<Achievement>(); }
|
||||
|
||||
// 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<Achievement> getAchievements() { return achievements; }
|
||||
|
||||
public void setAchievements(List<Achievement> achievements) { this.achievements = achievements; }
|
||||
// End Getters/Setters
|
||||
|
||||
public void addAchievement(Achievement achievement) { this.achievements.add(achievement); };
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
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<String> 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<String> getPlatforms() { return platforms; }
|
||||
|
||||
public void setPlatforms(List<String> platforms) { this.platforms = platforms; }
|
||||
|
||||
public void addToPlatforms(String platform) { this.platforms.add(platform); }
|
||||
// End Getters/Setters
|
||||
|
||||
}
|
||||
|
||||
@JsonProperty("games")
|
||||
private List<Game> games;
|
||||
|
||||
public Games() { games = new ArrayList<Game>(); }
|
||||
|
||||
// Start Getters/Setters
|
||||
public List<Game> getGames() { return games; }
|
||||
|
||||
public void setGames(List<Game> games) { this.games = games; }
|
||||
// End Getters/Setters
|
||||
|
||||
public void addGame(Game game) { this.games.add(game); }
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
166
backend/src/main/java/achievements/data/Profile.java
Normal file
|
@ -0,0 +1,166 @@
|
|||
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<Achievement> noteworthy;
|
||||
@JsonProperty("platforms")
|
||||
private List<Platform> platforms;
|
||||
@JsonProperty("ratings")
|
||||
private List<Rating> 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<Achievement> getNoteworthy() {
|
||||
return noteworthy;
|
||||
}
|
||||
|
||||
public void setNoteworthy(List<Achievement> noteworthy) {
|
||||
this.noteworthy = noteworthy;
|
||||
}
|
||||
|
||||
public List<Platform> getPlatforms() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
public void setPlatforms(List<Platform> platforms) {
|
||||
this.platforms = platforms;
|
||||
}
|
||||
|
||||
public List<Rating> getRatings() {
|
||||
return ratings;
|
||||
}
|
||||
|
||||
public void setRatings(List<Rating> ratings) {
|
||||
this.ratings = ratings;
|
||||
}
|
||||
}
|
58
backend/src/main/java/achievements/data/Session.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
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;
|
||||
}
|
||||
}
|
37
backend/src/main/java/achievements/data/User.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
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;
|
||||
}
|
||||
}
|
117
backend/src/main/java/achievements/data/request/SearchGames.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
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<Rating> 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<Rating> getRatings() {
|
||||
return ratings;
|
||||
}
|
||||
|
||||
public void setRatings(List<Rating> ratings) {
|
||||
this.ratings = ratings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
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;
|
||||
}
|
||||
}
|
41
backend/src/main/java/achievements/misc/APIList.java
Normal file
|
@ -0,0 +1,41 @@
|
|||
package achievements.misc;
|
||||
|
||||
import achievements.apis.PlatformAPI;
|
||||
import achievements.apis.SteamAPI;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class APIList {
|
||||
|
||||
@Autowired
|
||||
private RestTemplate rest;
|
||||
|
||||
public final Map<Integer, PlatformAPI> apis = new HashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
/*db = dbs.getConnection();
|
||||
try {
|
||||
|
||||
var stmt = db.prepareCall("{call GetPlatforms()}");
|
||||
var results = stmt.executeQuery();
|
||||
|
||||
while (results.next()) {
|
||||
var id = results.getInt("ID");
|
||||
|
||||
// Wanted to pull some skekery with dynamic class loading and external api jars, but...time is of the essence and I need to cut scope as much as possible
|
||||
apis.put(id, new ????(id, rest));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}*/
|
||||
|
||||
apis.put(0, new SteamAPI(0, rest));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package achievements.services;
|
||||
package achievements.misc;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -10,7 +10,7 @@ import java.sql.SQLException;
|
|||
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
|
||||
|
||||
@Component
|
||||
public class DbConnectionService {
|
||||
public class DbConnection {
|
||||
|
||||
private Connection connection;
|
||||
|
||||
|
@ -23,7 +23,7 @@ public class DbConnectionService {
|
|||
@Value("${database.user.password}")
|
||||
private String password;
|
||||
|
||||
public DbConnectionService() {}
|
||||
public DbConnection() {}
|
||||
|
||||
@PostConstruct
|
||||
public void connect() {
|
70
backend/src/main/java/achievements/misc/HashManager.java
Normal file
|
@ -0,0 +1,70 @@
|
|||
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;
|
||||
}
|
||||
}
|
33
backend/src/main/java/achievements/misc/Password.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
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;
|
||||
}
|
||||
}
|
73
backend/src/main/java/achievements/misc/SessionManager.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
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<String, Session> 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<String>();
|
||||
sessions.forEach((key, session) -> {
|
||||
if (!session.isUsed()) {
|
||||
remove.add(session.getKey());
|
||||
} else {
|
||||
session.setUsed(false);
|
||||
}
|
||||
});
|
||||
for (var session : remove) {
|
||||
sessions.remove(session);
|
||||
}
|
||||
}
|
||||
}
|
114
backend/src/main/java/achievements/services/APIService.java
Normal file
|
@ -0,0 +1,114 @@
|
|||
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<Integer>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
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<Achievement.Rating>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
34
backend/src/main/java/achievements/services/GameService.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package achievements.services;
|
||||
|
||||
import achievements.misc.DbConnection;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.sql.Connection;
|
||||
|
||||
@Service
|
||||
public class GameService {
|
||||
|
||||
@Autowired
|
||||
private DbConnection dbs;
|
||||
private Connection db;
|
||||
|
||||
@Autowired
|
||||
private ImageService imageService;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
db = dbs.getConnection();
|
||||
}
|
||||
|
||||
public String[] getIcon(int gameId) {
|
||||
try {
|
||||
var stmt = db.prepareCall("{call GetGameIcon(?)}");
|
||||
return imageService.getImageType(stmt, gameId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package achievements.services;
|
||||
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Service
|
||||
public class ImageService {
|
||||
public static final HashMap<String, String> MIME_TO_EXT = new HashMap<>();
|
||||
public static final HashMap<String, String> EXT_TO_MIME = new HashMap<>();
|
||||
static {
|
||||
MIME_TO_EXT.put("apng", "apng");
|
||||
MIME_TO_EXT.put("avif", "avif");
|
||||
MIME_TO_EXT.put("gif", "gif" );
|
||||
MIME_TO_EXT.put("jpeg", "jpg" );
|
||||
MIME_TO_EXT.put("png", "png" );
|
||||
MIME_TO_EXT.put("svg+xml", "svg" );
|
||||
MIME_TO_EXT.put("webp", "webp");
|
||||
|
||||
EXT_TO_MIME.put("apng", "apng" );
|
||||
EXT_TO_MIME.put("avif", "avif" );
|
||||
EXT_TO_MIME.put("gif", "gif" );
|
||||
EXT_TO_MIME.put("jpg", "jpeg" );
|
||||
EXT_TO_MIME.put("png", "png" );
|
||||
EXT_TO_MIME.put("svg", "svg+xml");
|
||||
EXT_TO_MIME.put("webp", "webp" );
|
||||
}
|
||||
|
||||
public String[] getImageType(CallableStatement stmt, int id) {
|
||||
try {
|
||||
stmt.setInt(1, id);
|
||||
|
||||
var result = stmt.executeQuery();
|
||||
if (result.next()) {
|
||||
var type = result.getString(1);
|
||||
if (type != null) {
|
||||
return new String[] { id + "." + type, EXT_TO_MIME.get(type) };
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void send(String[] image, String type, HttpServletResponse response) {
|
||||
var file = (File) null;
|
||||
var mimeType = (String) null;
|
||||
if (image == null) {
|
||||
file = new File("storage/images/default/" + type + ".png");
|
||||
mimeType = "png";
|
||||
} else {
|
||||
file = new File("storage/images/" + type + "/" + image[0]);
|
||||
mimeType = image[1];
|
||||
}
|
||||
try {
|
||||
var stream = new FileInputStream(file);
|
||||
IOUtils.copy(stream, response.getOutputStream());
|
||||
|
||||
response.setStatus(200);
|
||||
response.setContentType("image/" + mimeType);
|
||||
response.flushBuffer();
|
||||
stream.close();
|
||||
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
response.setStatus(500);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package achievements.services;
|
||||
|
||||
import achievements.misc.DbConnection;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.sql.Connection;
|
||||
|
||||
@Service
|
||||
public class 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;
|
||||
}
|
||||
}
|
132
backend/src/main/java/achievements/services/SearchService.java
Normal file
|
@ -0,0 +1,132 @@
|
|||
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<Achievement> searchAchievements(SearchAchievements query) {
|
||||
try {
|
||||
var stmt = db.prepareCall("{call SearchAchievements(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}");
|
||||
stmt.setString(1, query.getSearchTerm());
|
||||
stmt.setBoolean(3, query.isCompleted());
|
||||
if (query.getUserId() != null) { stmt.setInt (2, query.getUserId()); } else { stmt.setString(2, null); }
|
||||
if (query.getMinCompletion() != null) { stmt.setFloat(4, query.getMinCompletion()); } else { stmt.setString(4, null); }
|
||||
if (query.getMaxCompletion() != null) { stmt.setFloat(5, query.getMaxCompletion()); } else { stmt.setString(5, null); }
|
||||
if (query.getMinDifficulty() != null) { stmt.setFloat(6, query.getMinDifficulty()); } else { stmt.setString(6, null); }
|
||||
if (query.getMaxDifficulty() != null) { stmt.setFloat(7, query.getMaxDifficulty()); } else { stmt.setString(7, null); }
|
||||
if (query.getMinQuality() != null) { stmt.setFloat(8, query.getMinQuality()); } else { stmt.setString(8, null); }
|
||||
if (query.getMaxQuality() != null) { stmt.setFloat(9, query.getMaxQuality()); } else { stmt.setString(9, null); }
|
||||
stmt.setString(10, query.getOrdering());
|
||||
stmt.setString(11, query.getOrderDirection());
|
||||
var results = stmt.executeQuery();
|
||||
|
||||
var achievements = new ArrayList<Achievement>();
|
||||
while (results.next()) {
|
||||
var achievement = new Achievement();
|
||||
achievement.setID (results.getInt ("ID" ));
|
||||
achievement.setGame (results.getString("Game" ));
|
||||
achievement.setName (results.getString("Name" ));
|
||||
achievement.setCompletion(results.getInt ("Completion")); if (results.wasNull()) { achievement.setCompletion(null); }
|
||||
achievement.setDifficulty(results.getFloat ("Difficulty")); if (results.wasNull()) { achievement.setDifficulty(null); }
|
||||
achievement.setQuality (results.getFloat ("Quality" )); if (results.wasNull()) { achievement.setQuality (null); }
|
||||
achievements.add(achievement);
|
||||
}
|
||||
|
||||
return achievements;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<User> 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<User>();
|
||||
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<Game> 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<Game>();
|
||||
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;
|
||||
}
|
||||
}
|
256
backend/src/main/java/achievements/services/UserService.java
Normal file
|
@ -0,0 +1,256 @@
|
|||
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<Profile.Platform>();
|
||||
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<Profile.Rating>();
|
||||
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<Achievement> getNoteworthy(int userId) {
|
||||
try {
|
||||
var stmt = db.prepareCall("{call GetNoteworthyAchievementsForUser(?)}");
|
||||
stmt.setInt(1, userId);
|
||||
|
||||
var results = stmt.executeQuery();
|
||||
var achievements = new ArrayList<Achievement>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,8 @@
|
|||
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
|
3
frontend/.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
# Node files
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Import Data
|
||||
import.json
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
{}
|
||||
{
|
||||
"hosts": {
|
||||
"backend": "https://localhost:4730"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
"extends": [
|
||||
"config/base.json"
|
||||
],
|
||||
"hosts": {
|
||||
"frontend": "http://localhost:8080"
|
||||
},
|
||||
"build": "debug",
|
||||
"port": 8080
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
"extends": [
|
||||
"config/base.json"
|
||||
],
|
||||
"hosts": {
|
||||
"frontend": "http://localhost"
|
||||
},
|
||||
"build": "release",
|
||||
"port": 80
|
||||
}
|
||||
|
|
|
@ -3,17 +3,19 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Cross platform achievement tracker",
|
||||
"repository": "github:Gnarwhal/AchievementProject",
|
||||
"main": "static_server.js",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"debug": "node static_server.js config/debug.json",
|
||||
"release": "node static_server.js config/release.json"
|
||||
"debug": "node server.js config/debug.json",
|
||||
"release": "node server.js config/release.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"morgan": "^1.10.0",
|
||||
"xml2js": "^0.4.23"
|
||||
"passport": "^0.4.1",
|
||||
"passport-steam": "^1.0.15",
|
||||
"promptly": "^3.2.0"
|
||||
}
|
||||
}
|
||||
|
|
54
frontend/server.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
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();
|
|
@ -1,16 +0,0 @@
|
|||
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);
|
163
frontend/webpage/achievement.html
Normal file
|
@ -0,0 +1,163 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/achievement.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="achievement-page" class="page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Achievement</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="importing">
|
||||
<p id="importing-text">Contemplating...</p>
|
||||
<img id="importing-loading" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
<template data-template="achievement-page">
|
||||
<div id="achievement-section-0">
|
||||
<div id="achievement-info" class="page-subsection">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="achievement-info-subheader" class="page-subheader">
|
||||
<div id="achievement-info-flex" class="page-subheader-flex">
|
||||
<p id="achievement-name-text" class="page-subheader-text">${name}</p>
|
||||
<img id="achievement-icon-img" class="lazy-img" data-src="/api/achievement/${id}/image" alt="Achievement Icon" />
|
||||
</div>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<p id="achievement-description-text" class="page-subsection-chunk ap-text">${description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-section-1">
|
||||
<div id="achievement-stats" class="page-subsection">
|
||||
<div id="achievement-stats-numeric">
|
||||
<div id="achievement-completion" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Completion Rate</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div id="achievement-completion-stack">
|
||||
<img id="achievement-completion-background" src="/static/res/completion.svg">
|
||||
<canvas id="achievement-completion-canvas"></canvas>
|
||||
<p id="achievement-completion-text">${completion}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-difficulty" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Difficulty</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<p id="achievement-difficulty-text">${difficulty}</p>
|
||||
</div>
|
||||
<div id="achievement-quality" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Quality</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<p id="achievement-quality-text">${quality}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-rating" class="page-subsection">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<div class="page-subheader-flex">
|
||||
<p class="page-subheader-text">My Rating</p>
|
||||
<span id="rating-save-stack" class="achievement-save-stack">
|
||||
<img class="achievement-save page-subheader-icon" src="/static/res/save.svg" alt="Save Platforms" />
|
||||
<img class="achievement-save-hover page-subheader-icon" src="/static/res/save-hover.svg" alt="Save Platforms Hovered" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div id="achievement-rating-subsection" class="page-subsection-chunk">
|
||||
<div id="achievement-rating-numeric">
|
||||
<div id="achievement-difficulty-rating" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Difficulty</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="achievement-rating-text-flex">
|
||||
<input type="text" id="achievement-difficulty-rating-text" class="achievement-rating-text" value="${my_difficulty}"></input>
|
||||
<p class="achievement-rating-max-text">/ 10</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-quality-rating" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Quality</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="achievement-rating-text-flex">
|
||||
<input type="text" id="achievement-quality-rating-text" class="achievement-rating-text" value="${my_quality}"></input>
|
||||
<p class="achievement-rating-max-text">/ 10</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-description-rating" class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Review</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<textarea id="achievement-review-rating-text">${my_review}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="achievement-section-2">
|
||||
<div id="achievement-ratings" class="page-subsection">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Ratings</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon"></p>
|
||||
<p class="list-page-entry-text rating-username">Username</p>
|
||||
<p class="list-page-entry-text rating-difficulty">Difficulty</p>
|
||||
<p class="list-page-entry-text rating-quality">Quality</p>
|
||||
<p class="list-page-entry-text rating-review">Review</p>
|
||||
</div>
|
||||
<template data-template="rating-list: List<Basic>">
|
||||
<div class="list-page-entry rating" data-id="${user_id}">
|
||||
<img class="list-page-entry-icon lazy-img" data-src="/api/user/${user_id}/image" alt="User Image"></img>
|
||||
<p class="list-page-entry-text rating-username">${user_username}</p>
|
||||
<p class="list-page-entry-text rating-difficulty">${user_difficulty}</p>
|
||||
<p class="list-page-entry-text rating-quality">${user_quality}</p>
|
||||
<p class="list-page-entry-text rating-review">${user_review}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/achievement.js"></script>
|
||||
</body>
|
||||
</html>
|
39
frontend/webpage/import.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project | Import</title>
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/import.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar"></div>
|
||||
<div id="content-body">
|
||||
<div id="import-page" class="page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Import</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="import-dropzone" class="page-subsection">
|
||||
<div id="import-dropzone-wrapper" class="page-subsection-wrapper">
|
||||
<div id="upload-wrapper">
|
||||
<div id="upload-icon-stack">
|
||||
<img id="import-icon-base" src="/static/res/import.svg" alt="Import Icon">
|
||||
<img id="import-icon-hover" src="/static/res/import-hover.svg" alt="Import Icon Hover">
|
||||
</div>
|
||||
</div>
|
||||
<div id="import-console">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/import.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,35 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="styles/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<template data-template="content-body: List<Basic>">
|
||||
<div id="${page}-page" class="page">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">${title}</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
<template data-template="extern-${page}-page: Extern"></template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<script src="scripts/template.js"></script>
|
||||
<script src="scripts/index.js"></script>
|
||||
</body>
|
||||
</html>
|
51
frontend/webpage/login.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project | Login</title>
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/login.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar"></div>
|
||||
<div id="content-body">
|
||||
<div id="login-page" class="page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Achievements Project</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="login-flex">
|
||||
<div id="login-subsection" class="page-subsection">
|
||||
<div id="login-elements" class="page-subsection-wrapper">
|
||||
<div id="login-header" class="page-subheader">
|
||||
<p id="login-header-text" class="page-subheader-text">Login</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div id="login-form">
|
||||
<p id="error-message" class="form-row top multiline">Egg</p>
|
||||
<input id="email" class="login-field form-row" type="text" placeholder="Email"></input>
|
||||
<input id="username" class="login-field form-row" type="text" placeholder="Username"></input>
|
||||
<input id="password" class="login-field form-row" type="password" placeholder="Password"></input>
|
||||
<input id="confirm" class="login-field form-row" type="password" placeholder="Confirm your password"></input>
|
||||
<div id="button-row" class="form-row">
|
||||
<div id="create-user-button" class="ap-button login">Create Account</div>
|
||||
<div id="guest-login-button" class="ap-button login">Continue as Guest</div>
|
||||
</div>
|
||||
<div id="login-button" class="ap-button login form-row">Login</div>
|
||||
<p id="warning" class="form-row">WARNING!</p>
|
||||
<p id="warning-message" class="form-row multiline">The security of this project is questionable at best. Please refrain from using any truly sensitive data.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/login.js"></script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 135 KiB |
|
@ -1,95 +0,0 @@
|
|||
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();
|
||||
});
|
|
@ -1,198 +0,0 @@
|
|||
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(`(?:(?<!\\\\)\\\${${regexKey}})`, 'gm'), object[key]);
|
||||
if (typeof object[key] === 'object') {
|
||||
applyObject(object[key], path + key + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
applyObject(map, '');
|
||||
html = html.replace('\\&', '&');
|
||||
element.outerHTML = html.trim();
|
||||
});
|
||||
|
||||
// Extern - Retrieve template from webserver
|
||||
template.type.register('Extern', (element, name) => {
|
||||
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 = `<template></template>`;
|
||||
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]);
|
||||
})();
|
144
frontend/webpage/search_achievements.html
Normal file
|
@ -0,0 +1,144 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_achievements.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="search-achievements-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Achievement Search</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div id="list-page-search-filters">
|
||||
<div id="list-page-search-dropdown">
|
||||
<div id="search-wrapper" class="page-subsection-wrapper">
|
||||
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
|
||||
<label id="achievement-search-button" for="achievement-search">Search</label>
|
||||
<input id="achievement-search-field" type="text" placeholder="Game Name, Achievement Name" name="achievement-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
|
||||
<div id="filter-dropdown-stack">
|
||||
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
|
||||
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="list-page-filters-flex">
|
||||
<div id="personal-filters" class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Personal</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="completed-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Completed By Me</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Difficulty</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min. Completion</p>
|
||||
<input id="min-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max. Completion</p>
|
||||
<input id="max-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min. Difficulty</p>
|
||||
<input id="min-difficulty-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max. Difficulty</p>
|
||||
<input id="max-difficulty-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Quality</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min. Quality</p>
|
||||
<input id="min-quality-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max. Quality</p>
|
||||
<input id="max-quality-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-list-partition page-subsection-wrapper">
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon"></p>
|
||||
<p id="achievement-header-game" class="list-page-entry-text achievement-game-name">Game</p>
|
||||
<p id="achievement-header-name" class="list-page-entry-text achievement-name">Name</p>
|
||||
<p id="achievement-header-completion" class="list-page-entry-text achievement-completion">Completion Rate</p>
|
||||
<p id="achievement-header-difficulty" class="list-page-entry-text achievement-difficulty">Difficulty</p>
|
||||
<p id="achievement-header-quality" class="list-page-entry-text achievement-quality">Quality</p>
|
||||
</div>
|
||||
<template id="achievement-list-template" data-template="achievements-page-list: List<Basic>">
|
||||
<div class="list-page-entry achievement" data-id="${achievement_id}">
|
||||
<img class="list-page-entry-icon lazy-img" data-src="/api/achievement/${achievement_id}/image" alt="Achievement Icon"></img>
|
||||
<p class="list-page-entry-text achievement-game-name">${game_name}</p>
|
||||
<p class="list-page-entry-text achievement-name">${achievement_name}</p>
|
||||
<p class="list-page-entry-text achievement-completion">${completion}</p>
|
||||
<p class="list-page-entry-text achievement-difficulty">${difficulty}</p>
|
||||
<p class="list-page-entry-text achievement-quality">${quality}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_achievements.js"></script>
|
||||
</body>
|
||||
</html>
|
144
frontend/webpage/search_games.html
Normal file
|
@ -0,0 +1,144 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_games.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="search-games-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Game Search</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div id="list-page-search-filters">
|
||||
<div id="list-page-search-dropdown">
|
||||
<div id="search-wrapper" class="page-subsection-wrapper">
|
||||
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
|
||||
<label id="game-search-button" for="game-search">Search</label>
|
||||
<input id="game-search-field" type="text" placeholder="Name" name="game-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
|
||||
<div id="filter-dropdown-stack">
|
||||
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
|
||||
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="list-page-filters-flex">
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Me</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div id="owned-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">Owned By Me</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Achievements</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Avg. Completion</p>
|
||||
<input id="min-avg-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Avg. Completion</p>
|
||||
<input id="max-avg-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Users</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Num Owners</p>
|
||||
<input id="min-num-owners-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Num Owners</p>
|
||||
<input id="max-num-owners-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Num Perfects</p>
|
||||
<input id="min-num-perfects-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Num Perfects</p>
|
||||
<input id="max-num-perfects-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-list-partition page-subsection-wrapper">
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon game"></p>
|
||||
<p class="list-page-entry-text game-name">Game</p>
|
||||
<p class="list-page-entry-text game-achievement-count">Achievement Count</p>
|
||||
<p class="list-page-entry-text game-avg-completion">Avg. Completion</p>
|
||||
<p class="list-page-entry-text game-num-owners">Num Owners</p>
|
||||
<p class="list-page-entry-text game-num-perfects">Num Perfects</p>
|
||||
</div>
|
||||
<template id="game-list-template" data-template="games-page-list: List<Basic>">
|
||||
<div class="list-page-entry">
|
||||
<img class="list-page-entry-icon lazy-img game" data-src="/api/game/${game_id}/image" alt="Game Thumbnail"></img>
|
||||
<p class="list-page-entry-text game-name">${game_name}</p>
|
||||
<p class="list-page-entry-text game-achievement-count">${achievement_count}</p>
|
||||
<p class="list-page-entry-text game-avg-completion">${avg_completion}</p>
|
||||
<p class="list-page-entry-text game-num-owners">${num_owners}</p>
|
||||
<p class="list-page-entry-text game-num-perfects">${num_perfects}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_games.js"></script>
|
||||
</body>
|
||||
</html>
|
130
frontend/webpage/search_users.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Achievements Project</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/styles/theme.css" />
|
||||
<link rel="stylesheet" href="/static/styles/common.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search.css" />
|
||||
<link rel="stylesheet" href="/static/styles/search_users.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<template data-template="navbar: List<Basic>">
|
||||
<div id="navbar-section-${section}" class="navbar-section">
|
||||
<template data-template="navbar-section-${section}: List<Basic>">
|
||||
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
|
||||
${title}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div id="content-body">
|
||||
<div id="search-users-page" class="search page">
|
||||
<div class="page-subsection">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">User Search</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-subsection">
|
||||
<div id="list-page-search-filters">
|
||||
<div id="list-page-search-dropdown">
|
||||
<div id="search-wrapper" class="page-subsection-wrapper">
|
||||
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
|
||||
<label id="user-search-button" for="user-search">Search</label>
|
||||
<input id="user-search-field" type="text" placeholder="Name" name="user-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
|
||||
<div id="filter-dropdown-stack">
|
||||
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
|
||||
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="list-page-filters-flex">
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Games</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Owned</p>
|
||||
<input id="min-owned-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Owned</p>
|
||||
<input id="max-owned-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-filter-section page-subsection-wrapper">
|
||||
<div class="page-subheader">
|
||||
<p class="page-subheader-text">Achievements</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div class="list-page-filter-chunk page-subsection-chunk">
|
||||
<div class="page-subsection-wrapper">
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Completed</p>
|
||||
<input id="min-completed-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Completed</p>
|
||||
<input id="max-completed-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Min Avg. Completion</p>
|
||||
<input id="min-avg-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
<div class="list-page-filter">
|
||||
<p class="list-page-filter-label">Max Avg. Completion</p>
|
||||
<input id="max-avg-completion-filter" type="text" class="list-page-filter-param"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-list-partition page-subsection-wrapper">
|
||||
<div class="page-subsection-chunk">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-header">
|
||||
<p class="list-page-entry-icon"></p>
|
||||
<p class="list-page-entry-text user-username">Username</p>
|
||||
<p class="list-page-entry-text user-game-count">Game Count</p>
|
||||
<p class="list-page-entry-text user-achievement-count">Achievement Count</p>
|
||||
<p class="list-page-entry-text user-avg-completion">Avg. Completion</p>
|
||||
<p class="list-page-entry-text user-perfect-games">Perfect Games</p>
|
||||
</div>
|
||||
<template id="user-list-template" data-template="user-page-list: List<Basic>">
|
||||
<div class="list-page-entry user" data-id="${user_id}">
|
||||
<img class="list-page-entry-icon lazy-img" data-src="/api/user/${user_id}/image" alt="User Image"></img>
|
||||
<p class="list-page-entry-text user-username">${username}</p>
|
||||
<p class="list-page-entry-text user-game-count">${game_count}</p>
|
||||
<p class="list-page-entry-text user-achievement-count">${achievement_count}</p>
|
||||
<p class="list-page-entry-text user-avg-completion">${avg_completion}</p>
|
||||
<p class="list-page-entry-text user-perfect-games">${perfect_games}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/scripts/template.js"></script>
|
||||
<script src="/static/scripts/common.js"></script>
|
||||
<script src="/static/scripts/search.js"></script>
|
||||
<script src="/static/scripts/search_users.js"></script>
|
||||
</body>
|
||||
</html>
|
1
frontend/webpage/static/res/cancel-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M1717.59861,392.24859l-65.17222-65.17219a81.91631,81.91631,0,0,0-115.84716,0L1020.675,842.98065,504.77075,327.07642a81.91631,81.91631,0,0,0-115.84717,0L323.7514,392.2486a81.91631,81.91631,0,0,0,0,115.84716L839.65564,1024,323.75141,1539.90429a81.91631,81.91631,0,0,0,0,115.84717l65.17216,65.17214a81.91632,81.91632,0,0,0,115.84716,0l515.90425-515.9043,515.90425,515.90431a81.91633,81.91633,0,0,0,115.84715,0l65.1722-65.17216a81.91631,81.91631,0,0,0,0-115.84718L1201.69434,1024l515.90428-515.90423A81.91632,81.91632,0,0,0,1717.59861,392.24859Z"/></svg>
|
After Width: | Height: | Size: 716 B |
1
frontend/webpage/static/res/cancel.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M1717.59861,392.24859l-65.17222-65.17219a81.91631,81.91631,0,0,0-115.84716,0L1020.675,842.98065,504.77075,327.07642a81.91631,81.91631,0,0,0-115.84717,0L323.7514,392.2486a81.91631,81.91631,0,0,0,0,115.84716L839.65564,1024,323.75141,1539.90429a81.91631,81.91631,0,0,0,0,115.84717l65.17216,65.17214a81.91632,81.91632,0,0,0,115.84716,0l515.90425-515.9043,515.90425,515.90431a81.91633,81.91633,0,0,0,115.84715,0l65.1722-65.17216a81.91631,81.91631,0,0,0,0-115.84718L1201.69434,1024l515.90428-515.90423A81.91632,81.91632,0,0,0,1717.59861,392.24859Z"/></svg>
|
After Width: | Height: | Size: 716 B |
1
frontend/webpage/static/res/completion.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#44444c;}</style></defs><path class="cls-1" d="M500,0C223.8576,0,0,223.8576,0,500c0,276.14233,223.8576,500,500,500s500-223.85767,500-500C1000,223.8576,776.1424,0,500,0Zm0,955C248.71045,955,45,751.28955,45,500S248.71045,45,500,45,955,248.71045,955,500,751.28955,955,500,955Z"/></svg>
|
After Width: | Height: | Size: 404 B |
1
frontend/webpage/static/res/dropdown-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><polygon class="cls-1" points="1717.02 0 1354.981 0 512 842.981 330.98 1024 1354.98 2048 1717.02 2048 1717.02 1685.961 1055.058 1024 1717.02 362.039 1717.02 0"/></svg>
|
After Width: | Height: | Size: 310 B |
1
frontend/webpage/static/res/dropdown.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><polygon class="cls-1" points="1717.02 0 1354.981 0 512 842.981 330.98 1024 1354.98 2048 1717.02 2048 1717.02 1685.961 1055.058 1024 1717.02 362.039 1717.02 0"/></svg>
|
After Width: | Height: | Size: 310 B |
1
frontend/webpage/static/res/edit-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M1776.471,452.54834l-645.29239,645.29238a56.9113,56.9113,0,0,1-40.24239,16.66895H990.40167a56.91135,56.91135,0,0,1-56.91134-56.91134V957.06378a56.9113,56.9113,0,0,1,16.66895-40.24239L1595.45166,271.529Z"/><path class="cls-1" d="M1534.54675,1056.511v479.48889H512.00012V513.45325H991.48907l256.00007-256.00006H401.27344A145.273,145.273,0,0,0,256.00012,402.72656v1244a145.273,145.273,0,0,0,145.27332,145.27332h1244a145.27292,145.27292,0,0,0,145.27331-145.27332V800.511Z"/><path class="cls-1" d="M1738.98066,53.01934h203.9098a52.0902,52.0902,0,0,1,52.0902,52.0902v151.8196a52.0902,52.0902,0,0,1-52.0902,52.0902h-203.9098a0,0,0,0,1,0,0v-256a0,0,0,0,1,0,0Z" transform="translate(418.82598 1373.17402) rotate(-45)"/></svg>
|
After Width: | Height: | Size: 882 B |
1
frontend/webpage/static/res/edit.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M1776.471,452.54834l-645.29239,645.29238a56.9113,56.9113,0,0,1-40.24239,16.66895H990.40167a56.91135,56.91135,0,0,1-56.91134-56.91134V957.06378a56.9113,56.9113,0,0,1,16.66895-40.24239L1595.45166,271.529Z"/><path class="cls-1" d="M1534.54675,1056.511v479.48889H512.00012V513.45325H991.48907l256.00007-256.00006H401.27344A145.273,145.273,0,0,0,256.00012,402.72656v1244a145.273,145.273,0,0,0,145.27332,145.27332h1244a145.27292,145.27292,0,0,0,145.27331-145.27332V800.511Z"/><path class="cls-1" d="M1738.98066,53.01934h203.9098a52.0902,52.0902,0,0,1,52.0902,52.0902v151.8196a52.0902,52.0902,0,0,1-52.0902,52.0902h-203.9098a0,0,0,0,1,0,0v-256a0,0,0,0,1,0,0Z" transform="translate(418.82598 1373.17402) rotate(-45)"/></svg>
|
After Width: | Height: | Size: 882 B |
1
frontend/webpage/static/res/import-hover.svg
Normal file
After Width: | Height: | Size: 13 KiB |
1
frontend/webpage/static/res/import.svg
Normal file
After Width: | Height: | Size: 13 KiB |
1
frontend/webpage/static/res/loading.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M500,904C276.877,904,96,723.12305,96,500S276.877,96,500,96V0C223.8576,0,0,223.8576,0,500c0,276.14233,223.8576,500,500,500A498.43514,498.43514,0,0,0,853.55334,853.55334l-67.8822-67.8822A402.7356,402.7356,0,0,1,500,904Z"/></svg>
|
After Width: | Height: | Size: 392 B |
1
frontend/webpage/static/res/save-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M170.90729,1032.66212l72.83391-72.83391a76.49864,76.49864,0,0,1,108.18542,0l388.18146,388.18146,955.9653-955.9653a76.49864,76.49864,0,0,1,108.18542,0l72.83391,72.83391a76.49865,76.49865,0,0,1,0,108.18543L740.10808,1710.04834,170.90729,1140.84755A76.49865,76.49865,0,0,1,170.90729,1032.66212Z"/></svg>
|
After Width: | Height: | Size: 466 B |
1
frontend/webpage/static/res/save.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M170.90729,1032.66212l72.83391-72.83391a76.49864,76.49864,0,0,1,108.18542,0l388.18146,388.18146,955.9653-955.9653a76.49864,76.49864,0,0,1,108.18542,0l72.83391,72.83391a76.49865,76.49865,0,0,1,0,108.18543L740.10808,1710.04834,170.90729,1140.84755A76.49865,76.49865,0,0,1,170.90729,1032.66212Z"/></svg>
|
After Width: | Height: | Size: 466 B |
1
frontend/webpage/static/res/upload-hover.svg
Normal file
After Width: | Height: | Size: 19 KiB |
1
frontend/webpage/static/res/upload-invalid.svg
Normal file
After Width: | Height: | Size: 19 KiB |
1
frontend/webpage/static/res/upload.svg
Normal file
After Width: | Height: | Size: 19 KiB |
167
frontend/webpage/static/scripts/achievement.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
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();
|
||||
});
|
120
frontend/webpage/static/scripts/common.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
105
frontend/webpage/static/scripts/import.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
let consoleTop = true;
|
||||
let importConsole = null;
|
||||
const appendLine = (line) => {
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = `<p class="console-entry ${consoleTop ? 'top' : ''}">${line}</p>`
|
||||
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();
|
||||
});
|
185
frontend/webpage/static/scripts/login.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
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();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
26
frontend/webpage/static/scripts/search.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const expandTemplates = async () => {
|
||||
await commonTemplates();
|
||||
}
|
||||
|
||||
const loadFilters = () => {
|
||||
const filtersButton = document.querySelector("#filter-dropdown-stack");
|
||||
const filtersSection = document.querySelector("#list-page-filters-flex");
|
||||
|
||||
filtersButton.addEventListener("click", (clickEvent) => {
|
||||
filtersButton.classList.toggle("active");
|
||||
filtersSection.classList.toggle("active");
|
||||
});
|
||||
|
||||
const filterCheckboxes = document.querySelectorAll(".list-page-filter-checkbox");
|
||||
for (const checkbox of filterCheckboxes) {
|
||||
checkbox.parentElement.addEventListener("click", (clickEvent) => {
|
||||
checkbox.parentElement.classList.toggle("selected");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadCommonSearch = async () => {
|
||||
await loadCommon();
|
||||
|
||||
await expandTemplates();
|
||||
};
|
141
frontend/webpage/static/scripts/search_achievements.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
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();
|
||||
});
|
134
frontend/webpage/static/scripts/search_games.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
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();
|
||||
});
|
132
frontend/webpage/static/scripts/search_users.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
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();
|
||||
});
|
201
frontend/webpage/static/scripts/template.js
Normal file
|
@ -0,0 +1,201 @@
|
|||
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(`(?:(?<!\\\\)\\\${${regexKey}})`, 'gm'), object[key]);
|
||||
if (typeof object[key] === 'object') {
|
||||
applyObject(object[key], path + key + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
applyObject(map, '');
|
||||
html = html.replace('\\&', '&');
|
||||
element.outerHTML = html.trim();
|
||||
});
|
||||
|
||||
// Extern - Retrieve template from webserver
|
||||
template.register('Extern', (element, name) => {
|
||||
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 = `<template></template>`;
|
||||
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]);
|
||||
})();
|