diff --git a/ProjectLighthouse/Controllers/PublishController.cs b/ProjectLighthouse/Controllers/PublishController.cs index abb3978f..134521fa 100644 --- a/ProjectLighthouse/Controllers/PublishController.cs +++ b/ProjectLighthouse/Controllers/PublishController.cs @@ -25,7 +25,7 @@ namespace ProjectLighthouse.Controllers { Slot slot = await this.GetSlotFromBody(); if(slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded - string resource = LbpSerializer.StringElement("resource", slot.Resource); + string resource = LbpSerializer.StringElement("resource", slot.Resources); return this.Ok(LbpSerializer.TaggedStringElement("slot", resource, "type", "user")); } @@ -69,7 +69,7 @@ namespace ProjectLighthouse.Controllers { return this.Ok(); } - + public async Task GetSlotFromBody() { Request.Body.Position = 0; string bodyString = await new StreamReader(Request.Body).ReadToEndAsync(); diff --git a/ProjectLighthouse/Controllers/ResourcesController.cs b/ProjectLighthouse/Controllers/ResourcesController.cs index c7a677b4..83a1ba41 100644 --- a/ProjectLighthouse/Controllers/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/ResourcesController.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Text; using System.Threading.Tasks; @@ -26,10 +25,10 @@ namespace ProjectLighthouse.Controllers { [HttpGet("r/{hash}")] public IActionResult GetResource(string hash) { - string path = Path.Combine(Environment.CurrentDirectory, "r", hash); + string path = FileHelper.GetResourcePath(hash); - if(IOFile.Exists(path)) { - return this.File(IOFile.OpenRead(path), "image/jpg"); + if(FileHelper.ResourceExists(hash)) { + return this.File(IOFile.OpenRead(path), "application/octet-stream"); } return this.NotFound(); } @@ -37,11 +36,11 @@ namespace ProjectLighthouse.Controllers { // TODO: check if this is a valid hash [HttpPost("upload/{hash}")] public async Task UploadResource(string hash) { - string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r"); - string path = Path.Combine(assetsDirectory, hash); + string assetsDirectory = FileHelper.ResourcePath; + string path = FileHelper.GetResourcePath(hash); - if(!Directory.Exists(assetsDirectory)) Directory.CreateDirectory(assetsDirectory); - if(IOFile.Exists(path)) this.Ok(); // no reason to fail if it's already uploaded + FileHelper.EnsureDirectoryCreated(assetsDirectory); + if(FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded LbpFile file = new(Encoding.ASCII.GetBytes(await new StreamReader(Request.Body).ReadToEndAsync())); diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 80adc373..db9d0ace 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -22,6 +22,14 @@ namespace ProjectLighthouse { MySqlServerVersion.LatestSupportedServerVersion ); + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(nameof(Slot.ResourceCollection)) + .HasField(nameof(Slot.ResourceCollection)); + } + public async Task CreateUser(string username) { User user; if((user = await Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index a3768d6e..ba517aa9 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -1,10 +1,16 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using ProjectLighthouse.Types.Files; namespace ProjectLighthouse.Helpers { public static class FileHelper { + public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r"); + + public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash); + public static bool IsFileSafe(LbpFile file) { if(file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); @@ -38,5 +44,13 @@ namespace ProjectLighthouse.Helpers { _ => LbpFileType.Unknown, }; } + + public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash)); + + public static void EnsureDirectoryCreated(string path) { + if(!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path))); + } + + public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Migrations/20211020220840_ResourceList.Designer.cs b/ProjectLighthouse/Migrations/20211020220840_ResourceList.Designer.cs new file mode 100644 index 00000000..a94a22a6 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211020220840_ResourceList.Designer.cs @@ -0,0 +1,378 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using ProjectLighthouse; + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211020220840_ResourceList")] + partial class ResourceList + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.11"); + + modelBuilder.Entity("ProjectLighthouse.Types.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ResourceCollection") + .HasColumnType("longtext"); + + b.Property("RootLevel") + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.Token", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("BooHash") + .HasColumnType("longtext"); + + b.Property("CommentCount") + .HasColumnType("int"); + + b.Property("CommentsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("FavouriteSlotCount") + .HasColumnType("int"); + + b.Property("FavouriteUserCount") + .HasColumnType("int"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("HeartCount") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("Lists") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LolCatFtwCount") + .HasColumnType("int"); + + b.Property("PhotosByMeCount") + .HasColumnType("int"); + + b.Property("PhotosWithMeCount") + .HasColumnType("int"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("ReviewCount") + .HasColumnType("int"); + + b.Property("StaffChallengeBronzeCount") + .HasColumnType("int"); + + b.Property("StaffChallengeGoldCount") + .HasColumnType("int"); + + b.Property("StaffChallengeSilverCount") + .HasColumnType("int"); + + b.Property("UsedSlots") + .HasColumnType("int"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.Property("YayHash") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.Comment", b => + { + b.HasOne("ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.HeartedLevel", b => + { + b.HasOne("ProjectLighthouse.Types.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.QueuedLevel", b => + { + b.HasOne("ProjectLighthouse.Types.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.Slot", b => + { + b.HasOne("ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ProjectLighthouse.Types.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("ProjectLighthouse.Types.User", b => + { + b.HasOne("ProjectLighthouse.Types.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211020220840_ResourceList.cs b/ProjectLighthouse/Migrations/20211020220840_ResourceList.cs new file mode 100644 index 00000000..55e66249 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211020220840_ResourceList.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ProjectLighthouse.Migrations +{ + public partial class ResourceList : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Resource", + table: "Slots", + newName: "ResourceCollection"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "ResourceCollection", + table: "Slots", + newName: "Resource"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 3ff245ec..01e7212f 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -164,7 +164,7 @@ namespace ProjectLighthouse.Migrations b.Property("Name") .HasColumnType("longtext"); - b.Property("Resource") + b.Property("ResourceCollection") .HasColumnType("longtext"); b.Property("RootLevel") diff --git a/ProjectLighthouse/Types/Slot.cs b/ProjectLighthouse/Types/Slot.cs index f1856961..0d56bf32 100644 --- a/ProjectLighthouse/Types/Slot.cs +++ b/ProjectLighthouse/Types/Slot.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Xml.Serialization; @@ -29,9 +30,15 @@ namespace ProjectLighthouse.Types { [XmlElement("rootLevel")] public string RootLevel { get; set; } + + public string ResourceCollection; + [NotMapped] [XmlElement("resource")] - public string Resource { get; set; } + public string[] Resources { + get => this.ResourceCollection.Split(","); + set => this.ResourceCollection = string.Join(',', value); + } [XmlIgnore] public int LocationId { get; set; } @@ -84,7 +91,7 @@ namespace ProjectLighthouse.Types { LbpSerializer.StringElement("description", Description) + LbpSerializer.StringElement("icon", IconHash) + LbpSerializer.StringElement("rootLevel", RootLevel) + - LbpSerializer.StringElement("resource", Resource) + + LbpSerializer.StringElement("resource", this.Resources) + LbpSerializer.StringElement("location", Location.Serialize()) + LbpSerializer.StringElement("initiallyLocked", InitiallyLocked) + LbpSerializer.StringElement("isSubLevel", SubLevel) +