diff --git a/gaseous-server/Classes/Metadata/Storage.cs b/gaseous-server/Classes/Metadata/Storage.cs index 15eb9f1..792b2ec 100644 --- a/gaseous-server/Classes/Metadata/Storage.cs +++ b/gaseous-server/Classes/Metadata/Storage.cs @@ -229,7 +229,7 @@ namespace gaseous_server.Classes.Metadata case "alternativename": objectToStore = new IdentitiesOrValues(ids: fromJsonObject); break; - case "artworks": + case "artwork": objectToStore = new IdentitiesOrValues(ids: fromJsonObject); break; case "game": diff --git a/gaseous-server/Controllers/FilterController.cs b/gaseous-server/Controllers/FilterController.cs new file mode 100644 index 0000000..8247192 --- /dev/null +++ b/gaseous-server/Controllers/FilterController.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using gaseous_tools; +using IGDB.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class FilterController : ControllerBase + { + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public Dictionary Filter() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + Dictionary FilterSet = new Dictionary(); + + // platforms + List platforms = new List(); + string sql = "SELECT platform.id, platform.abbreviation, platform.alternativename, platform.`name`, platform.platformlogo, (SELECT COUNT(games_roms.id) AS RomCount FROM games_roms WHERE games_roms.platformid = platform.id) AS RomCount FROM platform HAVING RomCount > 0 ORDER BY `name`"; + DataTable dbResponse = db.ExecuteCMD(sql); + + foreach (DataRow dr in dbResponse.Rows) + { + platforms.Add(Classes.Metadata.Platforms.GetPlatform((long)dr["id"])); + } + FilterSet.Add("platforms", platforms); + + // genres + List genres = new List(); + sql = "SELECT DISTINCT t1.id, t1.`name` FROM genre AS t1 JOIN (SELECT * FROM game WHERE (SELECT COUNT(id) FROM games_roms WHERE gameid = game.id) > 0) AS t2 ON JSON_CONTAINS(t2.genres, CAST(t1.id AS char), '$') ORDER BY t1.`name`"; + dbResponse = db.ExecuteCMD(sql); + + foreach (DataRow dr in dbResponse.Rows) + { + genres.Add(Classes.Metadata.Genres.GetGenres((long)dr["id"])); + } + FilterSet.Add("genres", genres); + + return FilterSet; + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/GamesController.cs b/gaseous-server/Controllers/GamesController.cs index f80810e..04ee22a 100644 --- a/gaseous-server/Controllers/GamesController.cs +++ b/gaseous-server/Controllers/GamesController.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; +using gaseous_server.Classes.Metadata; using gaseous_tools; +using IGDB.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -14,8 +16,8 @@ namespace gaseous_server.Controllers public class GamesController : ControllerBase { [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public List Game(string name = "", string platform = "") + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult Game(string name = "", string platform = "", string genre = "", bool sortdescending = false) { string whereClause = ""; string havingClause = ""; @@ -51,6 +53,24 @@ namespace gaseous_server.Controllers whereClauses.Add(tempVal); } + if (genre.Length > 0) + { + tempVal = "("; + string[] genreClauseItems = genre.Split(","); + for (int i = 0; i < genreClauseItems.Length; i++) + { + if (i > 0) + { + tempVal += " AND "; + } + string genreLabel = "@genre" + i; + tempVal += "JSON_CONTAINS(game.genres, " + genreLabel + ", '$')"; + whereParams.Add(genreLabel, genreClauseItems[i]); + } + tempVal += ")"; + whereClauses.Add(tempVal); + } + // build where clause if (whereClauses.Count > 0) { @@ -59,7 +79,7 @@ namespace gaseous_server.Controllers { if (i > 0) { - whereClause += ", "; + whereClause += " AND "; } whereClause += whereClauses[i]; } @@ -73,14 +93,21 @@ namespace gaseous_server.Controllers { if (i > 0) { - havingClause += ", "; + havingClause += " AND "; } havingClause += havingClauses[i]; } } + // order by clause + string orderByClause = "ORDER BY `name` ASC"; + if (sortdescending == true) + { + orderByClause = "ORDER BY `name` DESC"; + } + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT DISTINCT games_roms.gameid AS ROMGameId, game.ageratings, game.aggregatedrating, game.aggregatedratingcount, game.alternativenames, game.artworks, game.bundles, game.category, game.collection, game.cover, game.dlcs, game.expansions, game.externalgames, game.firstreleasedate, game.`follows`, game.franchise, game.franchises, game.gameengines, game.gamemodes, game.genres, game.hypes, game.involvedcompanies, game.keywords, game.multiplayermodes, (CASE WHEN games_roms.gameid = 0 THEN games_roms.`name` ELSE game.`name` END) AS `name`, game.parentgame, game.platforms, game.playerperspectives, game.rating, game.ratingcount, game.releasedates, game.screenshots, game.similargames, game.slug, game.standaloneexpansions, game.`status`, game.storyline, game.summary, game.tags, game.themes, game.totalrating, game.totalratingcount, game.versionparent, game.versiontitle, game.videos, game.websites FROM gaseous.games_roms LEFT JOIN game ON game.id = games_roms.gameid " + whereClause + " " + havingClause + " ORDER BY `name`"; + string sql = "SELECT DISTINCT games_roms.gameid AS ROMGameId, game.ageratings, game.aggregatedrating, game.aggregatedratingcount, game.alternativenames, game.artworks, game.bundles, game.category, game.collection, game.cover, game.dlcs, game.expansions, game.externalgames, game.firstreleasedate, game.`follows`, game.franchise, game.franchises, game.gameengines, game.gamemodes, game.genres, game.hypes, game.involvedcompanies, game.keywords, game.multiplayermodes, (CASE WHEN games_roms.gameid = 0 THEN games_roms.`name` ELSE game.`name` END) AS `name`, game.parentgame, game.platforms, game.playerperspectives, game.rating, game.ratingcount, game.releasedates, game.screenshots, game.similargames, game.slug, game.standaloneexpansions, game.`status`, game.storyline, game.summary, game.tags, game.themes, game.totalrating, game.totalratingcount, game.versionparent, game.versiontitle, game.videos, game.websites FROM gaseous.games_roms LEFT JOIN game ON game.id = games_roms.gameid " + whereClause + " " + havingClause + " " + orderByClause; List RetVal = new List(); @@ -98,7 +125,293 @@ namespace gaseous_server.Controllers } } - return RetVal; + return Ok(RetVal); + } + + [HttpGet] + [Route("{GameId}")] + [ProducesResponseType(typeof(Game), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult Game(long GameId) + { + try + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + if (gameObject != null) + { + return Ok(gameObject); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/artwork")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult GameArtwork(long GameId) + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List artworks = new List(); + if (gameObject.Artworks != null) + { + foreach (long ArtworkId in gameObject.Artworks.Ids) + { + Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + artworks.Add(GameArtwork); + } + } + + return Ok(artworks); + } + + [HttpGet] + [Route("{GameId}/artwork/{ArtworkId}")] + [ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameArtwork(long GameId, long ArtworkId) + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + try + { + IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (artworkObject != null) + { + return Ok(artworkObject); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + + } + + [HttpGet] + [Route("{GameId}/artwork/{ArtworkId}/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameCoverImage(long GameId, long ArtworkId) + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + try + { + IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (artworkObject != null) { + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork", artworkObject.ImageId + ".png"); + if (System.IO.File.Exists(coverFilePath)) + { + string filename = artworkObject.ImageId + ".png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/cover")] + [ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameCover(long GameId) + { + try + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + if (gameObject != null) + { + IGDB.Models.Cover coverObject = Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (coverObject != null) + { + return Ok(coverObject); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/cover/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameCoverImage(long GameId) + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Cover.png"); + if (System.IO.File.Exists(coverFilePath)) { + string filename = "Cover.png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/screenshots")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult GameScreenshot(long GameId) + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List screenshots = new List(); + if (gameObject.Screenshots != null) + { + foreach (long ScreenshotId in gameObject.Screenshots.Ids) + { + Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + screenshots.Add(GameScreenshot); + } + } + + return Ok(screenshots); + } + + [HttpGet] + [Route("{GameId}/screenshots/{ScreenshotId}")] + [ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameScreenshot(long GameId, long ScreenshotId) + { + try + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + if (gameObject != null) { + IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (screenshotObject != null) + { + return Ok(screenshotObject); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/screenshots/{ScreenshotId}/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameScreenshotImage(long GameId, long ScreenshotId) + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots", screenshotObject.ImageId + ".png"); + if (System.IO.File.Exists(coverFilePath)) + { + string filename = screenshotObject.ImageId + ".png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/videos")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult GameVideo(long GameId) + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List videos = new List(); + if (gameObject.Videos != null) + { + foreach (long VideoId in gameObject.Videos.Ids) + { + GameVideo gameVideo = GamesVideos.GetGame_Videos(VideoId); + videos.Add(gameVideo); + } + } + + return Ok(videos); } } } diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index c3ee292..00eea39 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -28,6 +28,9 @@ builder.Services.AddControllers().AddJsonOptions(x => { // serialize enums as strings in api responses (e.g. Role) x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + + // suppress nulls + x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/gaseous-tools/Database/MySQL/gaseous-1000.sql b/gaseous-tools/Database/MySQL/gaseous-1000.sql index 5e2b19f..457cd2d 100644 --- a/gaseous-tools/Database/MySQL/gaseous-1000.sql +++ b/gaseous-tools/Database/MySQL/gaseous-1000.sql @@ -361,8 +361,8 @@ CREATE TABLE `platform` ( `createdat` datetime DEFAULT NULL, `generation` int DEFAULT NULL, `name` varchar(45) DEFAULT NULL, - `platformfamily` int DEFAULT NULL, - `platformlogo` int DEFAULT NULL, + `platformfamily` bigint DEFAULT NULL, + `platformlogo` bigint DEFAULT NULL, `slug` varchar(45) DEFAULT NULL, `summary` longtext, `updatedat` datetime DEFAULT NULL,