Added searching for all relevant data types

This commit is contained in:
Gnarwhal 2021-02-18 17:21:23 -05:00
parent 4df0a804b3
commit a8cf583569
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
39 changed files with 1159 additions and 233 deletions

View file

@ -60,7 +60,10 @@ public class SteamAPI extends PlatformAPI {
var newGame = new APIResponse.Game(); var newGame = new APIResponse.Game();
newGame.setPlatformGameId(Integer.toString(game.getAppid())); newGame.setPlatformGameId(Integer.toString(game.getAppid()));
newGame.setName(game.getName()); newGame.setName(game.getName());
newGame.setThumbnail("https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/" + game.getAppid() + "/" + game.getImg_logo_url() + ".jpg"); // 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); newGame.setPlayed(game.getPlaytime_forever() > 0);
var achievements = new HashMap<String, APIResponse.Game.Achievement>(); var achievements = new HashMap<String, APIResponse.Game.Achievement>();
@ -72,7 +75,6 @@ public class SteamAPI extends PlatformAPI {
.queryParam("appid", game.getAppid()) .queryParam("appid", game.getAppid())
.toUriString(); .toUriString();
var schemaResponse = rest.exchange(gameSchemaUrl, HttpMethod.GET, entity, GetSchemaForGameBody.class).getBody().getGame().getAvailableGameStats(); var schemaResponse = rest.exchange(gameSchemaUrl, HttpMethod.GET, entity, GetSchemaForGameBody.class).getBody().getGame().getAvailableGameStats();
if (schemaResponse != null && schemaResponse.getAchievements() != null) { if (schemaResponse != null && schemaResponse.getAchievements() != null) {
for (var schema : schemaResponse.getAchievements()) { for (var schema : schemaResponse.getAchievements()) {

View file

@ -1,6 +1,8 @@
package achievements.controllers; package achievements.controllers;
import achievements.data.query.SearchAchievements; import achievements.data.request.SearchAchievements;
import achievements.data.request.SearchGames;
import achievements.data.request.SearchUsers;
import achievements.services.SearchService; import achievements.services.SearchService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -23,4 +25,24 @@ public class SearchController {
return ResponseEntity.badRequest().body("[]"); 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("[]");
}
}
} }

View file

@ -2,9 +2,9 @@ package achievements.controllers;
import achievements.data.APError; import achievements.data.APError;
import achievements.data.APPostRequest; import achievements.data.APPostRequest;
import achievements.data.query.AddPlatform; import achievements.data.request.AddPlatform;
import achievements.data.query.RemovePlatform; import achievements.data.request.RemovePlatform;
import achievements.data.query.SetUsername; import achievements.data.request.SetUsername;
import achievements.services.ImageService; import achievements.services.ImageService;
import achievements.services.UserService; import achievements.services.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View file

@ -1,31 +0,0 @@
package achievements.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class Game {
@JsonProperty("ID")
private int id;
@JsonProperty("name")
private String name;
@JsonProperty("platforms")
private List<String> platforms;
@JsonProperty("achievementCount")
private int achievementCount;
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); }
}

View file

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

View file

@ -1,4 +1,4 @@
package achievements.data.query; package achievements.data.request;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View file

@ -1,4 +1,4 @@
package achievements.data.query; package achievements.data.request;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View file

@ -1,4 +1,4 @@
package achievements.data.query; package achievements.data.request;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -22,6 +22,10 @@ public class SearchAchievements {
private Float minQuality; private Float minQuality;
@JsonProperty("maxQuality") @JsonProperty("maxQuality")
private Float maxQuality; private Float maxQuality;
@JsonProperty("ordering")
private String ordering;
@JsonProperty("orderDirection")
private String orderDirection;
public String getSearchTerm() { public String getSearchTerm() {
return searchTerm; return searchTerm;
@ -94,4 +98,20 @@ public class SearchAchievements {
public void setMaxQuality(Float maxQuality) { public void setMaxQuality(Float maxQuality) {
this.maxQuality = 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;
}
} }

View 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;
}
}

View file

@ -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;
}
}

View file

@ -1,4 +1,4 @@
package achievements.data.query; package achievements.data.request;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View file

@ -1,4 +1,4 @@
package achievements.data; package achievements.data.response.search;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -11,6 +11,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.sql.Connection; import java.sql.Connection;
import java.sql.Types; import java.sql.Types;
import java.util.HashSet;
@Service @Service
public class APIService { public class APIService {
@ -39,7 +40,7 @@ public class APIService {
var addIfNotGame = db.prepareCall("{call AddIfNotGame(?, ?, ?)}"); var addIfNotGame = db.prepareCall("{call AddIfNotGame(?, ?, ?)}");
var addGameToPlatform = db.prepareCall("{call AddGameToPlatform(?, ?, ?)}"); var addGameToPlatform = db.prepareCall("{call AddGameToPlatform(?, ?, ?)}");
var addGameToUser = db.prepareCall("{call AddGameToPlatform(?, ?, ?)}"); var addGameToUser = db.prepareCall("{call AddGameToUser(?, ?, ?)}");
var addIfNotAchievement = db.prepareCall("{call AddIfNotAchievement(?, ?, ?, ?, ?, ?)}"); var addIfNotAchievement = db.prepareCall("{call AddIfNotAchievement(?, ?, ?, ?, ?, ?)}");
var setAchievementProgressForUser = db.prepareCall("{call SetAchievementProgressForUser(?, ?, ?, ?)}"); var setAchievementProgressForUser = db.prepareCall("{call SetAchievementProgressForUser(?, ?, ?, ?)}");
@ -70,6 +71,7 @@ public class APIService {
addGameToUser.setInt(3, platformId); addGameToUser.setInt(3, platformId);
addGameToUser.execute(); addGameToUser.execute();
var set = new HashSet<Integer>();
for (var achievement : game.getAchievements()) { for (var achievement : game.getAchievements()) {
addIfNotAchievement.setInt(1, gameId); addIfNotAchievement.setInt(1, gameId);
addIfNotAchievement.setString(2, achievement.getName()); addIfNotAchievement.setString(2, achievement.getName());
@ -78,6 +80,7 @@ public class APIService {
addIfNotAchievement.setString(5, getFileType(achievement.getThumbnail())); addIfNotAchievement.setString(5, getFileType(achievement.getThumbnail()));
addIfNotAchievement.execute(); addIfNotAchievement.execute();
var achievementId = addIfNotAchievement.getInt(6); var achievementId = addIfNotAchievement.getInt(6);
set.add(achievementId);
var achievementIcon = new File("storage/images/achievement/" + achievementId + "." + getFileType(achievement.getThumbnail())); var achievementIcon = new File("storage/images/achievement/" + achievementId + "." + getFileType(achievement.getThumbnail()));
if (!achievementIcon.exists()) { if (!achievementIcon.exists()) {

View file

@ -24,7 +24,7 @@ public class GameService {
public String[] getIcon(int gameId) { public String[] getIcon(int gameId) {
try { try {
var stmt = db.prepareCall("{call GetAchievementIcon(?)}"); var stmt = db.prepareCall("{call GetGameIcon(?)}");
return imageService.getImageType(stmt, gameId); return imageService.getImageType(stmt, gameId);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -1,7 +1,11 @@
package achievements.services; package achievements.services;
import achievements.data.Achievement; import achievements.data.request.SearchGames;
import achievements.data.query.SearchAchievements; 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 achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -9,8 +13,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.*;
import java.util.List;
@Service @Service
public class SearchService { public class SearchService {
@ -26,16 +29,18 @@ public class SearchService {
public List<Achievement> searchAchievements(SearchAchievements query) { public List<Achievement> searchAchievements(SearchAchievements query) {
try { try {
var stmt = db.prepareCall("{call SearchAchievements(?, ?, ?, ?, ?, ?, ?, ?, ?)}"); var stmt = db.prepareCall("{call SearchAchievements(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}");
stmt.setString(1, query.getSearchTerm()); stmt.setString(1, query.getSearchTerm());
stmt.setBoolean(3, query.isCompleted()); stmt.setBoolean(3, query.isCompleted());
if (query.getUserId() != null) { stmt.setInt( 2, query.getUserId() ); } else { stmt.setString(2, null); } 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.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.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.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.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.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); } 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 results = stmt.executeQuery();
var achievements = new ArrayList<Achievement>(); var achievements = new ArrayList<Achievement>();
@ -56,4 +61,72 @@ public class SearchService {
} }
return null; 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;
}
} }

View file

@ -1,9 +1,9 @@
package achievements.services; package achievements.services;
import achievements.data.Profile; import achievements.data.Profile;
import achievements.data.query.AddPlatform; import achievements.data.request.AddPlatform;
import achievements.data.query.RemovePlatform; import achievements.data.request.RemovePlatform;
import achievements.data.query.SetUsername; import achievements.data.request.SetUsername;
import achievements.misc.DbConnection; import achievements.misc.DbConnection;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -210,4 +210,6 @@ public class UserService {
} }
return -1; return -1;
} }
} }

View file

@ -4,3 +4,5 @@ spring.jackson.default-property-inclusion=always
server.session.cookie.secure = false server.session.cookie.secure = false
spring.servlet.multipart.max-file-size = 10MB
spring.servlet.multipart.max-request-size = 10MB

View file

@ -11,14 +11,10 @@ const promptly = require('promptly');
const config = require('./config.js').load(process.argv[2]); const config = require('./config.js').load(process.argv[2]);
if (config.build === 'debug') {
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
}
console.log(`Running server at '${config.hosts.frontend}'`); console.log(`Running server at '${config.hosts.frontend}'`);
passport.use(new SteamStrategy({ passport.use(new SteamStrategy({
returnURL: `${config.hosts.frontend}/profile/steam`, returnURL: `${config.hosts.frontend}/user/steam`,
realm: `${config.hosts.frontend}`, realm: `${config.hosts.frontend}`,
profile: false, profile: false,
})); }));
@ -41,8 +37,11 @@ app.get("/users", (req, res) => {
app.get("/games", (req, res) => { app.get("/games", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/search_games.html")); res.sendFile(path.join(__dirname + "/webpage/search_games.html"));
}); });
app.get("/profile/:id", (req, res) => { app.get("/achievement/:id", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/profile.html")); 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) => {}); app.get("/auth/steam", passport.authenticate('steam'), (req, res) => {});

View file

@ -25,7 +25,7 @@
<div id="search-achievements-page" class="search page"> <div id="search-achievements-page" class="search page">
<div class="page-subsection"> <div class="page-subsection">
<div class="page-header"> <div class="page-header">
<p class="page-header-text">Search Achievements</p> <p class="page-header-text">Achievement Search</p>
<div class="page-header-separator"></div> <div class="page-header-separator"></div>
</div> </div>
</div> </div>
@ -35,7 +35,7 @@
<div id="search-wrapper" class="page-subsection-wrapper"> <div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk"> <div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label id="achievement-search-button" for="achievement-search">Search</label> <label id="achievement-search-button" for="achievement-search">Search</label>
<input id="achievement-search-field" type="text" placeholder="Name" name="achievement-search"/> <input id="achievement-search-field" type="text" placeholder="Game Name, Achievement Name" name="achievement-search"/>
</div> </div>
</div> </div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper"> <div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
@ -46,16 +46,16 @@
</div> </div>
</div> </div>
<div id="list-page-filters-flex"> <div id="list-page-filters-flex">
<div class="list-page-filter-section page-subsection-wrapper"> <div id="personal-filters" class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader"> <div class="page-subheader">
<p class="page-subheader-text">Me</p> <p class="page-subheader-text">Personal</p>
<div class="page-subheader-separator"></div> <div class="page-subheader-separator"></div>
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="completed-filter" class="list-page-filter"> <div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p> <p class="list-page-filter-name">Completed By Me</p>
</div> </div>
</div> </div>
</div> </div>
@ -68,19 +68,19 @@
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Min Completion</p> <p class="list-page-filter-label">Min. Completion</p>
<input id="min-completion-filter" type="text" class="list-page-filter-param"></input> <input id="min-completion-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Max Completion</p> <p class="list-page-filter-label">Max. Completion</p>
<input id="max-completion-filter" type="text" class="list-page-filter-param"></input> <input id="max-completion-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Min Difficulty</p> <p class="list-page-filter-label">Min. Difficulty</p>
<input id="min-difficulty-filter" type="text" class="list-page-filter-param"></input> <input id="min-difficulty-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Max Difficulty</p> <p class="list-page-filter-label">Max. Difficulty</p>
<input id="max-difficulty-filter" type="text" class="list-page-filter-param"></input> <input id="max-difficulty-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
</div> </div>
@ -94,11 +94,11 @@
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Min Quality</p> <p class="list-page-filter-label">Min. Quality</p>
<input id="min-quality-filter" type="text" class="list-page-filter-param"></input> <input id="min-quality-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div class="list-page-filter"> <div class="list-page-filter">
<p class="list-page-filter-label">Max Quality</p> <p class="list-page-filter-label">Max. Quality</p>
<input id="max-quality-filter" type="text" class="list-page-filter-param"></input> <input id="max-quality-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
</div> </div>
@ -112,15 +112,15 @@
<div class="list-page-list"> <div class="list-page-list">
<div class="list-page-header"> <div class="list-page-header">
<p class="list-page-entry-icon"></p> <p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-game-name">Game</p> <p id="achievement-header-game" class="list-page-entry-text achievement-game-name">Game</p>
<p class="list-page-entry-text achievement-name">Name</p> <p id="achievement-header-name" class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-completion">Completion Rate</p> <p id="achievement-header-completion" class="list-page-entry-text achievement-completion">Completion Rate</p>
<p class="list-page-entry-text achievement-difficulty">Difficulty</p> <p id="achievement-header-difficulty" class="list-page-entry-text achievement-difficulty">Difficulty</p>
<p class="list-page-entry-text achievement-quality">Quality</p> <p id="achievement-header-quality" class="list-page-entry-text achievement-quality">Quality</p>
</div> </div>
<template id="achievement-list-template" data-template="achievements-page-list: List<Basic>"> <template id="achievement-list-template" data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry"> <div id="achievement-entry-${achievement_id}" class="list-page-entry achievement">
<img class="list-page-entry-icon lazy-img" data-src="/api/achievement/${achievement_id}/image" alt="Achievement Thumbnail"></img> <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-game-name">${game_name}</p>
<p class="list-page-entry-text achievement-name">${achievement_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-completion">${completion}</p>

View file

@ -25,7 +25,7 @@
<div id="search-games-page" class="search page"> <div id="search-games-page" class="search page">
<div class="page-subsection"> <div class="page-subsection">
<div class="page-header"> <div class="page-header">
<p class="page-header-text">Search Games</p> <p class="page-header-text">Game Search</p>
<div class="page-header-separator"></div> <div class="page-header-separator"></div>
</div> </div>
</div> </div>
@ -34,8 +34,8 @@
<div id="list-page-search-dropdown"> <div id="list-page-search-dropdown">
<div id="search-wrapper" class="page-subsection-wrapper"> <div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk"> <div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label for="achievement-search">Search</label> <label id="game-search-button" for="game-search">Search</label>
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/> <input id="game-search-field" type="text" placeholder="Name" name="game-search"/>
</div> </div>
</div> </div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper"> <div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
@ -48,14 +48,14 @@
<div id="list-page-filters-flex"> <div id="list-page-filters-flex">
<div class="list-page-filter-section page-subsection-wrapper"> <div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader"> <div class="page-subheader">
<p class="page-subheader-text">Games</p> <p class="page-subheader-text">Me</p>
<div class="page-subheader-separator"></div> <div class="page-subheader-separator"></div>
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter"> <div id="owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p> <p class="list-page-filter-name">Owned By Me</p>
</div> </div>
</div> </div>
</div> </div>
@ -67,31 +67,39 @@
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Min Avg. Completion</p>
<p class="list-page-filter-name">My Games</p> <input id="min-avg-completion-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div id="in-progress-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Max Avg. Completion</p>
<p class="list-page-filter-name">In Progress</p> <input id="max-avg-completion-filter" type="text" class="list-page-filter-param"></input>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="list-page-filter-section page-subsection-wrapper"> <div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader"> <div class="page-subheader">
<p class="page-subheader-text">Platforms</p> <p class="page-subheader-text">Users</p>
<div class="page-subheader-separator"></div> <div class="page-subheader-separator"></div>
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Min Num Owners</p>
<p class="list-page-filter-name">Games Owned</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>
@ -103,25 +111,28 @@
<div class="page-subsection-chunk"> <div class="page-subsection-chunk">
<div class="list-page-list"> <div class="list-page-list">
<div class="list-page-header"> <div class="list-page-header">
<p class="list-page-entry-icon"></p> <p class="list-page-entry-icon game"></p>
<p class="list-page-entry-text achievement-name">Name</p> <p class="list-page-entry-text game-name">Game</p>
<p class="list-page-entry-text achievement-description">Description</p> <p class="list-page-entry-text game-achievement-count">Achievement Count</p>
<p class="list-page-entry-text achievement-stages">Stages</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> </div>
<template data-template="achievements-page-list: List<Basic>"> <template id="game-list-template" data-template="games-page-list: List<Basic>">
<div class="list-page-entry"> <div class="list-page-entry">
<img class="list-page-entry-icon" src="/static/res/dummy_achievement.png" alt="Achievement Thumbnail"></img> <img class="list-page-entry-icon lazy-img game" data-src="/api/game/${game_id}/image" alt="Game Thumbnail"></img>
<div class="list-page-entry-text-section"> <p class="list-page-entry-text game-name">${game_name}</p>
<p class="list-page-entry-text achievement-name">${achievement-name}</p> <p class="list-page-entry-text game-achievement-count">${achievement_count}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p> <p class="list-page-entry-text game-avg-completion">${avg_completion}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p> <p class="list-page-entry-text game-num-owners">${num_owners}</p>
</div> <p class="list-page-entry-text game-num-perfects">${num_perfects}</p>
</div> </div>
</template> </template>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -25,7 +25,7 @@
<div id="search-users-page" class="search page"> <div id="search-users-page" class="search page">
<div class="page-subsection"> <div class="page-subsection">
<div class="page-header"> <div class="page-header">
<p class="page-header-text">Search Users</p> <p class="page-header-text">User Search</p>
<div class="page-header-separator"></div> <div class="page-header-separator"></div>
</div> </div>
</div> </div>
@ -34,8 +34,8 @@
<div id="list-page-search-dropdown"> <div id="list-page-search-dropdown">
<div id="search-wrapper" class="page-subsection-wrapper"> <div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk"> <div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label for="achievement-search">Search</label> <label id="user-search-button" for="user-search">Search</label>
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/> <input id="user-search-field" type="text" placeholder="Name" name="user-search"/>
</div> </div>
</div> </div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper"> <div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
@ -53,45 +53,39 @@
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Min Owned</p>
<p class="list-page-filter-name">Games 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> </div>
</div> </div>
<div class="list-page-filter-section page-subsection-wrapper"> <div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader"> <div class="page-subheader">
<p class="page-subheader-text">General</p> <p class="page-subheader-text">Achievements</p>
<div class="page-subheader-separator"></div> <div class="page-subheader-separator"></div>
</div> </div>
<div class="list-page-filter-chunk page-subsection-chunk"> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper"> <div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Min Completed</p>
<p class="list-page-filter-name">From My Games</p> <input id="min-completed-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div id="completed-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Max Completed</p>
<p class="list-page-filter-name">Completed</p> <input id="max-completed-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
<div id="completed-filter" class="list-page-filter"> <div class="list-page-filter">
<div class="list-page-filter-checkbox"></div> <p class="list-page-filter-label">Min Avg. Completion</p>
<p class="list-page-filter-name">Completed</p> <input id="min-avg-completion-filter" type="text" class="list-page-filter-param"></input>
</div> </div>
</div> <div class="list-page-filter">
</div> <p class="list-page-filter-label">Max Avg. Completion</p>
</div> <input id="max-avg-completion-filter" type="text" class="list-page-filter-param"></input>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Platforms</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div> </div>
</div> </div>
</div> </div>
@ -104,24 +98,27 @@
<div class="list-page-list"> <div class="list-page-list">
<div class="list-page-header"> <div class="list-page-header">
<p class="list-page-entry-icon"></p> <p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-name">Name</p> <p class="list-page-entry-text user-username">Username</p>
<p class="list-page-entry-text achievement-description">Description</p> <p class="list-page-entry-text user-game-count">Game Count</p>
<p class="list-page-entry-text achievement-stages">Stages</p> <p class="list-page-entry-text user-achievement-count">Achievement Count</p>
</div> <p class="list-page-entry-text user-avg-completion">Avg. Completion</p>
<template data-template="achievements-page-list: List<Basic>"> <p class="list-page-entry-text user-perfect-games">Perfect Games</p>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="/static/res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<div class="list-page-entry-text-section">
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div> </div>
<template 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> </div>
</template> </template>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<img id="loading-results" class="ap-loading" src="/static/res/loading.svg" alt="Loading Symbol" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -63,7 +63,7 @@ const commonTemplates = async () => {
]); ]);
} else { } else {
template.apply("navbar-section-right").values([ template.apply("navbar-section-right").values([
{ item: "login", title: "Login" } { item: "login", title: "Login / Create Account" }
]); ]);
} }
}; };
@ -96,7 +96,7 @@ const connectNavbar = () => {
window.location.href = "/login"; window.location.href = "/login";
}); });
} else if (item.dataset.pageName === "profile") { } else if (item.dataset.pageName === "profile") {
item.addEventListener("click", (clickEvent) => window.location.href = `/profile/${session.id}`); item.addEventListener("click", (clickEvent) => window.location.href = `/user/${session.id}`);
} else { } else {
item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`); item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`);
} }

View file

@ -91,7 +91,7 @@ window.addEventListener("load", async (loadEvent) => {
const data = response.data; const data = response.data;
if (response.status === 201) { if (response.status === 201) {
session = data; session = data;
window.location.href = "/"; window.location.href = `/user/${session.id}`;
} else if (response.status === 500) { } else if (response.status === 500) {
raiseError([], "Internal server error :("); raiseError([], "Internal server error :(");
} else { } else {

View file

@ -10,6 +10,10 @@ const saveTemplate = () => {
const loadAchievementSearch = () => { const loadAchievementSearch = () => {
const loading = document.querySelector("#loading-results"); 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 searchButton = document.querySelector("#achievement-search-button");
const searchField = document.querySelector("#achievement-search-field" ); const searchField = document.querySelector("#achievement-search-field" );
@ -22,6 +26,8 @@ const loadAchievementSearch = () => {
const minQuality = document.querySelector("#min-quality-filter" ); const minQuality = document.querySelector("#min-quality-filter" );
const maxQuality = document.querySelector("#max-quality-filter" ); const maxQuality = document.querySelector("#max-quality-filter" );
let ordering = 'name';
let direction = true;
let canSearch = true; let canSearch = true;
const loadList = async () => { const loadList = async () => {
if (canSearch) { if (canSearch) {
@ -29,16 +35,17 @@ const loadAchievementSearch = () => {
const body = { const body = {
searchTerm: searchField.value, searchTerm: searchField.value,
userId: completed.classList.contains('active') ? session.id : null, userId: completed.classList.contains('selected') ? session.id : null,
completed: completed.classList.contains('active'), completed: completed.classList.contains('selected') ? true : null,
minCompletion: minCompletion.value === '' ? null : Number(minCompletion.value), minCompletion: minCompletion.value === '' ? null : Number(minCompletion.value),
maxCompletion: maxCompletion.value === '' ? null : Number(maxCompletion.value), maxCompletion: maxCompletion.value === '' ? null : Number(maxCompletion.value),
minDifficulty: minDifficulty.value === '' ? null : Number(minDifficulty.value), minDifficulty: minDifficulty.value === '' ? null : Number(minDifficulty.value),
maxDifficulty: maxDifficulty.value === '' ? null : Number(maxDifficulty.value), maxDifficulty: maxDifficulty.value === '' ? null : Number(maxDifficulty.value),
minQuality: minQuality.value === '' ? null : Number(minQuality.value ), minQuality: minQuality.value === '' ? null : Number(minQuality.value ),
maxQuality: maxQuality.value === '' ? null : Number(maxQuality.value ), maxQuality: maxQuality.value === '' ? null : Number(maxQuality.value ),
ordering: ordering,
orderDirection: direction ? 'ASC' : 'DESC',
}; };
console.log(body);
let successful = true; 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.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.maxCompletion)) { successful = false; maxCompletion.style.backgroundColor = 'var(--error)'; } else { maxCompletion.style.backgroundColor = 'var(--foreground)'; }
@ -72,9 +79,9 @@ const loadAchievementSearch = () => {
achievement_id: item.ID, achievement_id: item.ID,
achievement_name: item.name, achievement_name: item.name,
game_name: item.game, game_name: item.game,
completion: item.completion == null ? 'N/A' : item.completion + '%', completion: item.completion == null ? 'N/A' : `${item.completion}%`,
difficulty: item.difficulty == null ? 'N/A' : item.difficulty + ' / 10', difficulty: item.difficulty == null ? 'N/A' : `${item.difficulty} / 10`,
quality: item.quality == null ? 'N/A' : item.quality + ' / 10' quality: item.quality == null ? 'N/A' : `${item.quality} / 10`
})))); }))));
await template.expand(); await template.expand();
data.then(data => { data.then(data => {
@ -82,6 +89,25 @@ const loadAchievementSearch = () => {
canSearch = true; canSearch = true;
loadLazyImages(); loadLazyImages();
}); });
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();
});
}
} }
}; };

View file

@ -0,0 +1,135 @@
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 => {
console.log(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();
});

View 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();
});

View file

@ -160,8 +160,11 @@ const loadProfile = () => {
document.querySelector("#platform-0"), document.querySelector("#platform-0"),
]; ];
let allowSteamImport = true;
steamButtons[0].addEventListener("click", (clickEvent) => { steamButtons[0].addEventListener("click", (clickEvent) => {
if (allowSteamImport) {
window.location.href = "/auth/steam"; window.location.href = "/auth/steam";
}
}); });
steamButtons[1].addEventListener("click", (clickEvent) => { steamButtons[1].addEventListener("click", (clickEvent) => {
fetch(`/api/user/${profileId}/platforms/remove`, { fetch(`/api/user/${profileId}/platforms/remove`, {
@ -170,7 +173,10 @@ const loadProfile = () => {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ sessionKey: session.key, platformId: 0 }) body: JSON.stringify({ sessionKey: session.key, platformId: 0 })
}).then(() => {
allowSteamImport = true;
}); });
allowSteamImport = false;
steamButtons[1].parentElement.classList.remove("connected"); steamButtons[1].parentElement.classList.remove("connected");
}); });

View file

@ -289,6 +289,9 @@
color: var(--foreground); color: var(--foreground);
font-size: 24px; font-size: 24px;
transition-property: background-color;
transition-duration: 0.15s;
} }
.list-page-entry-icon { .list-page-entry-icon {
@ -309,6 +312,8 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
cursor: default;
} }
.list-page-entry > .list-page-entry-text { .list-page-entry > .list-page-entry-text {
@ -318,6 +323,17 @@
.list-page-header > .list-page-entry-text { .list-page-header > .list-page-entry-text {
border-left: 1px solid var(--accent-value0); border-left: 1px solid var(--accent-value0);
transition-property: background-color;
transition-duration: 0.15s;
}
.list-page-header > .list-page-entry-text:hover {
background-color: var(--accent-value1);
}
.list-page-header > .list-page-entry-text:active {
background-color: var(--accent-value0);
} }
#loading-results { #loading-results {

View file

@ -1,3 +1,7 @@
.list-page-entry.achievement:hover {
background-color: var(--background-light);
}
.list-page-entry-text.achievement-game-name { flex-grow: 1.75; } .list-page-entry-text.achievement-game-name { flex-grow: 1.75; }
.list-page-entry-text.achievement-name { flex-grow: 2; } .list-page-entry-text.achievement-name { flex-grow: 2; }
.list-page-entry-text.achievement-completion { flex-grow: 1; } .list-page-entry-text.achievement-completion { flex-grow: 1; }

View file

@ -0,0 +1,9 @@
.list-page-entry-icon.game {
width: 137px;
}
.list-page-entry-text.game-name { flex-grow: 2.5; }
.list-page-entry-text.game-achievement-count { flex-grow: 1; }
.list-page-entry-text.game-avg-completion { flex-grow: 1; }
.list-page-entry-text.game-num-owners { flex-grow: 1; }
.list-page-entry-text.game-num-perfects { flex-grow: 1; }

View file

@ -0,0 +1,9 @@
.list-page-entry.user:hover {
background-color: var(--background-light);
}
.list-page-entry-text.user-username { flex-grow: 1.5; }
.list-page-entry-text.user-game-count { flex-grow: 1; }
.list-page-entry-text.user-achievement-count { flex-grow: 1; }
.list-page-entry-text.user-avg-completion { flex-grow: 1; }
.list-page-entry-text.user-perfect-games { flex-grow: 1; }

View file

@ -1,10 +1,11 @@
:root { :root {
--background-dark: #111117; --background-dark: #111117;
--background: #22222A; --background: #22222A;
--background-light: #33333B;
--distinction: #44444C;
--foreground-disabled: #77777D; --foreground-disabled: #77777D;
--foreground-dark: #AAAAAA; --foreground-dark: #AAAAAA;
--foreground: #EEEEEE; --foreground: #EEEEEE;
--distinction: #44444C;
--accent-hue: 0; --accent-hue: 0;

View file

@ -6,7 +6,7 @@
<link rel="stylesheet" href="/static/styles/theme.css" /> <link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" /> <link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/profile.css" /> <link rel="stylesheet" href="/static/styles/user.css" />
</head> </head>
<body> <body>
<div id="navbar"> <div id="navbar">
@ -137,6 +137,6 @@
</div> </div>
<script src="/static/scripts/template.js"></script> <script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script> <script src="/static/scripts/common.js"></script>
<script src="/static/scripts/profile.js"></script> <script src="/static/scripts/user.js"></script>
</body> </body>
</html> </html>

View file

@ -2,7 +2,7 @@
-- GET USER NAME AND STATS PROCEDURE -- -- GET USER NAME AND STATS PROCEDURE --
--------------------------------------- ---------------------------------------
CREATE PROCEDURE GetUserNameAndStats( ALTER PROCEDURE GetUserNameAndStats(
@userId INT, @userId INT,
@username VARCHAR(32) OUTPUT, @username VARCHAR(32) OUTPUT,
@completed INT OUTPUT, @completed INT OUTPUT,
@ -42,7 +42,7 @@ SELECT * FROM [User]
-- GET USER PLATFORMS PROCEDURE -- -- GET USER PLATFORMS PROCEDURE --
---------------------------------- ----------------------------------
CREATE PROCEDURE GetUserPlatforms( ALTER PROCEDURE GetUserPlatforms(
@userId INT @userId INT
) )
AS AS
@ -62,7 +62,7 @@ GO
-- GET USER RATINGS PROCEDURE -- -- GET USER RATINGS PROCEDURE --
-------------------------------- --------------------------------
CREATE PROCEDURE GetUserRatings( ALTER PROCEDURE GetUserRatings(
@userId INT @userId INT
) )
AS AS
@ -83,7 +83,7 @@ GO
-- GET USER IMAGE PROCEDURE -- -- GET USER IMAGE PROCEDURE --
------------------------------ ------------------------------
CREATE PROCEDURE GetUserImage( ALTER PROCEDURE GetUserImage(
@userId INT @userId INT
) )
AS AS
@ -100,7 +100,7 @@ GO
-- SET USERNAME -- -- SET USERNAME --
------------------ ------------------
CREATE PROCEDURE SetUsername( ALTER PROCEDURE SetUsername(
@userId INT, @userId INT,
@username VARCHAR(32) @username VARCHAR(32)
) )
@ -118,7 +118,7 @@ GO
-- SET USER IMAGE PROCEDURE -- -- SET USER IMAGE PROCEDURE --
------------------------------ ------------------------------
CREATE PROCEDURE SetUserImage( ALTER PROCEDURE SetUserImage(
@userId INT, @userId INT,
@type ImageType, @type ImageType,
@oldType ImageType OUTPUT @oldType ImageType OUTPUT
@ -138,7 +138,7 @@ GO
-- ADD USER TO PLATFORM -- -- ADD USER TO PLATFORM --
-------------------------- --------------------------
CREATE PROCEDURE AddUserToPlatform( ALTER PROCEDURE AddUserToPlatform(
@userId INT, @userId INT,
@platformId INT, @platformId INT,
@platformUserID VARCHAR(32) @platformUserID VARCHAR(32)
@ -167,7 +167,7 @@ GO
-- REMOVE USER FROM PLATFORM -- -- REMOVE USER FROM PLATFORM --
------------------------------- -------------------------------
CREATE PROCEDURE RemoveUserFromPlatform( ALTER PROCEDURE RemoveUserFromPlatform(
@userId INT, @userId INT,
@platformId INT @platformId INT
) )
@ -197,7 +197,7 @@ GO
-- ADD PLATFORM -- -- ADD PLATFORM --
------------------ ------------------
CREATE PROCEDURE AddPlatform( ALTER PROCEDURE AddPlatform(
@name VARCHAR(32), @name VARCHAR(32),
@platformId INT OUTPUT @platformId INT OUTPUT
) )
@ -216,7 +216,7 @@ GO
-- REMOVE PLATFORM -- -- REMOVE PLATFORM --
--------------------- ---------------------
CREATE PROCEDURE RemovePlatform( ALTER PROCEDURE RemovePlatform(
@platformId INT @platformId INT
) )
AS AS
@ -238,7 +238,7 @@ GO
-- GET PLATFORMS -- -- GET PLATFORMS --
------------------- -------------------
CREATE PROCEDURE GetPlatforms ALTER PROCEDURE GetPlatforms
AS AS
SELECT ID, PlatformName FROM [Platform] SELECT ID, PlatformName FROM [Platform]
RETURN 0 RETURN 0
@ -248,7 +248,7 @@ GO
-- GET PLATFORM NAME -- -- GET PLATFORM NAME --
----------------------- -----------------------
CREATE PROCEDURE GetPlatformName( ALTER PROCEDURE GetPlatformName(
@platformId INT, @platformId INT,
@name VARCHAR(32) OUTPUT @name VARCHAR(32) OUTPUT
) )
@ -266,7 +266,7 @@ GO
-- GET PLATFORM ICON -- -- GET PLATFORM ICON --
----------------------- -----------------------
CREATE PROCEDURE GetPlatformIcon( ALTER PROCEDURE GetPlatformIcon(
@platformId INT @platformId INT
) )
AS AS
@ -283,7 +283,7 @@ GO
-- ADD GAME -- -- ADD GAME --
-------------- --------------
CREATE PROCEDURE AddGame( ALTER PROCEDURE AddGame(
@name VARCHAR(32), @name VARCHAR(32),
@image ImageType, @image ImageType,
@gameId INT OUTPUT @gameId INT OUTPUT
@ -308,7 +308,7 @@ GO
-- ADD IF NOT GAME -- -- ADD IF NOT GAME --
--------------------- ---------------------
CREATE PROCEDURE AddIfNotGame( ALTER PROCEDURE AddIfNotGame(
@name VARCHAR(32), @name VARCHAR(32),
@image VARCHAR(11), @image VARCHAR(11),
@gameId INT OUTPUT @gameId INT OUTPUT
@ -332,7 +332,7 @@ GO
-- REMOVE GAME -- -- REMOVE GAME --
----------------- -----------------
CREATE PROCEDURE RemoveGame( ALTER PROCEDURE RemoveGame(
@gameId INT @gameId INT
) )
AS AS
@ -496,7 +496,7 @@ GO
-- ADD ACHIEVEMENT -- -- ADD ACHIEVEMENT --
--------------------- ---------------------
CREATE PROCEDURE AddAchievement( ALTER PROCEDURE AddAchievement(
@gameId INT, @gameId INT,
@name VARCHAR(128), @name VARCHAR(128),
@description VARCHAR(512), @description VARCHAR(512),

View file

@ -1,8 +1,8 @@
----------------------- -------------------------
-- SEARCH ACHIEVEMENTS -- -- SEARCH ACHIEVEMENTS --
----------------------- -------------------------
CREATE PROCEDURE SearchAchievements( ALTER PROCEDURE SearchAchievements(
@searchTerm VARCHAR(32), @searchTerm VARCHAR(32),
@userId INT, @userId INT,
@completed BIT, @completed BIT,
@ -11,7 +11,9 @@ CREATE PROCEDURE SearchAchievements(
@minDifficulty FLOAT, @minDifficulty FLOAT,
@maxDifficulty FLOAT, @maxDifficulty FLOAT,
@minQuality FLOAT, @minQuality FLOAT,
@maxQuality FLOAT @maxQuality FLOAT,
@orderBy VARCHAR(16),
@orderDirection VARCHAR(4)
) )
AS AS
IF @userId IS NULL AND @completed = 1 IF @userId IS NULL AND @completed = 1
@ -20,41 +22,179 @@ BEGIN
RETURN 1 RETURN 1
END END
IF @completed IS NULL
SET @completed = 0
IF @searchTerm IS NULL OR @searchTerm = '' IF @searchTerm IS NULL OR @searchTerm = ''
SET @searchTerm = '%' SET @searchTerm = '%'
ELSE ELSE
SET @searchTerm = '%' + @searchTerm + '%' SET @searchTerm = '%' + @searchTerm + '%'
PRINT @searchTerm
IF NOT @userId IS NULL SELECT TOP 100 Achievement.ID, Game.[Name] AS Game, Achievement.[Name], Completion, Difficulty, Quality
SELECT TOP 100 Game.[Name] AS Game, Achievement.[Name], Completion, Difficulty, Quality FROM Achievement
FROM Achievement JOIN Game ON Game.ID = GameID
JOIN MaxProgress ON AchievementID = Achievement.ID AND UserID = @userId JOIN AchievementCompletion AC ON AC.ID = Achievement.ID
JOIN Game ON Game.ID = GameID JOIN AchievementRatings AR ON AR.ID = Achievement.ID
JOIN AchievementCompletion AC ON AC.ID = Achievement.ID WHERE (Game.[Name] LIKE @searchTerm OR Achievement.[Name] LIKE @searchTerm)
JOIN AchievementRatings AR ON AR.ID = Achievement.ID AND (@completed <> 1 OR Achievement.ID IN (SELECT AchievementID FROM MaxProgress WHERE UserID = @userId AND Progress = Stages))
WHERE (Game.[Name] LIKE @searchTerm OR Achievement.[Name] LIKE @searchTerm)
AND (@completed <> 1 OR Progress = Stages )
AND (@minCompletion IS NULL OR @minCompletion <= Completion)
AND (@maxCompletion IS NULL OR @maxCompletion >= Completion)
AND (@minDifficulty IS NULL OR @minDifficulty <= Difficulty)
AND (@maxDifficulty IS NULL OR @maxDifficulty >= Difficulty)
AND (@minQuality IS NULL OR @minQuality <= Quality )
AND (@maxQuality IS NULL OR @maxQuality >= Quality )
ELSE
SELECT TOP 100 Achievement.ID, Game.[Name] AS Game, Achievement.[Name], Completion, Quality, Difficulty
FROM Achievement
JOIN Game ON Game.ID = GameID
JOIN AchievementCompletion AC ON AC.ID = Achievement.ID
JOIN AchievementRatings AR ON AR.ID = Achievement.ID
WHERE (Game.[Name] LIKE @searchTerm OR Achievement.[Name] LIKE @searchTerm)
AND (@minCompletion IS NULL OR @minCompletion <= Completion) AND (@minCompletion IS NULL OR @minCompletion <= Completion)
AND (@maxCompletion IS NULL OR @maxCompletion >= Completion) AND (@maxCompletion IS NULL OR @maxCompletion >= Completion)
AND (@minDifficulty IS NULL OR @minDifficulty <= Difficulty) AND (@minDifficulty IS NULL OR @minDifficulty <= Difficulty)
AND (@maxDifficulty IS NULL OR @maxDifficulty >= Difficulty) AND (@maxDifficulty IS NULL OR @maxDifficulty >= Difficulty)
AND (@minQuality IS NULL OR @minQuality <= Quality ) AND (@minQuality IS NULL OR @minQuality <= Quality )
AND (@maxQuality IS NULL OR @maxQuality >= Quality ) AND (@maxQuality IS NULL OR @maxQuality >= Quality )
ORDER BY
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END ASC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Game' THEN Game.[Name] ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN Achievement.[Name] ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Completion' THEN Completion ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Difficulty' THEN Difficulty ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Quality' THEN Quality ELSE NULL END DESC
RETURN 0 RETURN 0
GO GO
EXEC SearchAchievements '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ------------------
-- SEARCH USERS --
------------------
ALTER PROCEDURE SearchUsers(
@searchTerm VARCHAR(32),
@minOwned INT,
@maxOwned INT,
@minCompleted INT,
@maxCompleted INT,
@minAvgCompletion INT,
@maxAvgCompletion INT,
@orderBy VARCHAR(16),
@orderDirection VARCHAR(4)
)
AS
IF @searchTerm IS NULL OR @searchTerm = ''
SET @searchTerm = '%'
ELSE
SET @searchTerm = '%' + @searchTerm + '%'
SELECT TOP 100 *
FROM (
SELECT [User].ID, Username, ISNULL(GameCount, 0) AS GameCount, ISNULL(AchievementCount, 0) AS AchievementCount, AvgCompletion, ISNULL(PerfectGames, 0) AS PerfectGames
FROM [User]
LEFT JOIN (
SELECT
UserID,
COUNT(GameID) AS GameCount,
SUM(Completed) AS AchievementCount,
AVG((Completed * 100) / Total) AS AvgCompletion,
SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS PerfectGames
FROM GameCompletionByUser
GROUP BY UserID
) AS Completion ON Completion.UserID = [User].ID
) AS Results
WHERE (Username LIKE @searchTerm)
AND (@minOwned IS NULL OR @minOwned <= GameCount )
AND (@maxOwned IS NULL OR @maxOwned >= GameCount )
AND (@minCompleted IS NULL OR @minCompleted <= AchievementCount)
AND (@maxCompleted IS NULL OR @maxCompleted >= AchievementCount)
AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion )
AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion )
ORDER BY
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Username' THEN Username ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END ASC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Username' THEN Username ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'GameCount' THEN GameCount ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'PerfectCount' THEN PerfectGames ELSE NULL END DESC
RETURN 0
GO
------------------
-- SEARCH GAMES --
------------------
ALTER PROCEDURE SearchGames(
@searchTerm VARCHAR(32),
@userId INT,
@owned BIT,
@minAvgCompletion INT,
@maxAvgCompletion INT,
@minNumOwners INT,
@maxNumOwners INT,
@minNumPerfects INT,
@maxNumPerfects INT,
@orderBy VARCHAR(16),
@orderDirection VARCHAR(4)
)
AS
IF @userId IS NULL AND @owned = 1
BEGIN
PRINT 'Cannot search for owned games with no user specified'
RETURN 1
END
PRINT 'UserID, Owned'
PRINT @userId
PRINT @owned
IF @owned IS NULL
SET @owned = 0
IF @searchTerm IS NULL OR @searchTerm = ''
SET @searchTerm = '%'
ELSE
SET @searchTerm = '%' + @searchTerm + '%'
SELECT TOP 100 *
FROM (
SELECT
Game.ID,
[Name],
AchievementCount,
AvgCompletion,
ISNULL(NumOwners, 0) AS NumOwners,
ISNULL(NumPerfects, 0) AS NumPerfects
FROM Game
LEFT JOIN (
SELECT
GameID,
Total AS AchievementCount,
AVG((Completed * 100) / Total) AS AvgCompletion,
SUM(CASE WHEN Completed = Total THEN 1 ELSE 0 END) AS NumPerfects
FROM GameCompletionByUser
GROUP BY GameID, Total
) AS Completion ON Completion.GameID = Game.ID
LEFT JOIN (
SELECT GameID, COUNT(UserID) AS NumOwners
FROM OwnsUnique
GROUP BY GameID
) AS Owners ON Owners.GameID = Game.ID
) AS Results
WHERE ([Name] LIKE @searchTerm)
AND (@owned <> 1 OR ID IN (SELECT GameID FROM OwnsUnique WHERE UserID = @userId))
AND (@minAvgCompletion IS NULL OR @minAvgCompletion <= AvgCompletion)
AND (@maxAvgCompletion IS NULL OR @maxAvgCompletion >= AvgCompletion)
AND (@minNumOwners IS NULL OR @minNumOwners <= NumOwners )
AND (@maxNumOwners IS NULL OR @maxNumOwners >= NumOwners )
AND (@minNumPerfects IS NULL OR @minNumPerfects <= NumPerfects )
AND (@maxNumPerfects IS NULL OR @maxNumPerfects >= NumPerfects )
ORDER BY
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END ASC,
CASE WHEN @orderDirection = 'ASC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END ASC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'Name' THEN [Name] ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AchievementCount' THEN AchievementCount ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'AvgCompletion' THEN AvgCompletion ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumOwners' THEN NumOwners ELSE NULL END DESC,
CASE WHEN @orderDirection = 'DESC' AND @orderBy = 'NumPerfects' THEN NumPerfects ELSE NULL END DESC
RETURN 0
GO
EXEC SearchGames '', 3, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL

View file

@ -33,3 +33,10 @@ AS
GROUP BY Achievement.ID GROUP BY Achievement.ID
GO GO
-- List of games owned by a user removing duplicate ownership if owned on multiple platforms
CREATE VIEW OwnsUnique
AS
SELECT UserID, GameID
FROM Owns
GROUP BY UserID, GameID
GO