diff --git a/MorePracticeMalodyServer/Controllers/StoreController.cs b/MorePracticeMalodyServer/Controllers/StoreController.cs index b5ea51d..562676d 100644 --- a/MorePracticeMalodyServer/Controllers/StoreController.cs +++ b/MorePracticeMalodyServer/Controllers/StoreController.cs @@ -17,9 +17,7 @@ * * Known bugs: * - * When search keyword at start of Title, nothing returns. - * This is caused by EF core sql generate. - * This only tested using sqlite. + * */ using System; @@ -74,8 +72,7 @@ public async Task> GetList(int uid, int api, string word, int int lvle, int beta, int from) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server not support api version {api}."); + Util.CheckVersion(api); var resp = new Response(); // Max Items server will return. @@ -102,20 +99,20 @@ public async Task> GetList(int uid, int api, string word, int if (lvle != 0) temp = temp.Where(c => c.Level < lvle); - var result = temp + var result = await temp .Select(c => c.Song) .Distinct() .OrderBy(s => s.SongId) .Skip(from * maxItem) .Take(maxItem) - .ToList(); //TODO: Save to cache. + .ToListAsync(); //TODO: Save to cache. resp.Code = 0; // Add song to return value. foreach (var song in result) resp.Data.Add(new SongInfo { - Artist = song.Artist, + Artist = org == 0 ? song.Artist : song.OriginalArtist, Bpm = song.Bpm, Cover = song.Cover, Length = song.Length, @@ -129,19 +126,22 @@ public async Task> GetList(int uid, int api, string word, int } // Try to find data from memory cache first. - if (cache.TryGetValue(word.ToLower(), out List cachedSongList)) + var keyword = Util.TrimSpecial(word).ToLower(); + if (cache.TryGetValue(keyword, out List cachedSongList)) { } else // Query db and write result to cache { - var result = context.Songs - .Where(s => s.Title.Contains(word.ToLower()) || s.OriginalTitle.Contains(word.ToLower())) + var result = await context.Songs + .Include(s => s.Charts) + .Where(s => s.SearchString.Contains(keyword) || + s.OriginalSearchString.Contains(keyword)) + .AsSplitQuery() .AsNoTracking() - .ToList(); //BUG: This always return a empty list. Why? - // This is a bug of ef core :( + .ToListAsync(); // Create new cache entry. Set value abd expiration. - var cacheEntry = cache.CreateEntry(word); + var cacheEntry = cache.CreateEntry(keyword); cacheEntry.Value = result; cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(0, 2, 0); @@ -157,10 +157,10 @@ public async Task> GetList(int uid, int api, string word, int song.Charts = song.Charts .Where(c => c.Type == ChartState.Stable) .ToList(); - - song.Charts = song.Charts - .Where(c => c.Level > lvge && c.Level < lvle) // select level - .ToList(); + if (lvle != 0 && lvge != 0) + song.Charts = song.Charts + .Where(c => c.Level > lvge && c.Level < lvle) // select level + .ToList(); if (mode != -1) song.Charts = song.Charts @@ -219,11 +219,47 @@ public async Task> GetList(int uid, int api, string word, int public async Task> GetPromote(int uid, int api, int org, int mode, int from) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server not support api version {api}."); + Util.CheckVersion(api); - //TODO - return new Response(); + var resp = new Response(); + var maxCount = 50; + + // Query promotes from database. + var result = await context.Promotions + .Include(p => p.Song) + .AsNoTracking() + .ToListAsync(); // TODO: Cache result? + + resp.Code = 0; + // To see if has more. + if (result.Count - maxCount * from > maxCount) + { + resp.HasMore = true; + resp.Next = from + 1; + } + else + { + resp.HasMore = false; + } + + // Insert to Data + for (var i = from * maxCount; resp.HasMore ? i != from * maxCount + maxCount : i != result.Count; i++) + { + var song = result[i].Song; + resp.Data.Add(new SongInfo + { + Artist = org == 0 ? song.Artist : song.OriginalArtist, + Bpm = song.Bpm, + Cover = song.Cover, + Length = song.Length, + Mode = song.Mode, + Sid = song.SongId, + Time = GetTimeStamp(song.Time), + Title = org == 0 ? song.Title : song.OriginalTitle + }); + } + + return resp; } /// @@ -241,8 +277,7 @@ public async Task> GetPromote(int uid, int api, int org, int public async Task> GetChart(int uid, int api, int sid, int beta, int mode, int from) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server not support api version {api}."); + Util.CheckVersion(api); var resp = new Response(); // Max Items server will return. @@ -324,8 +359,7 @@ public async Task> GetChart(int uid, int api, int sid, int b public async Task> QuerySong(int uid, int api, int sid = -1, int cid = -1, int org = 0) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server does not support api version {api}."); + Util.CheckVersion(api); // If not providing a valid SID or CID, throw a exception. if (sid == -1 && cid == -1) @@ -375,6 +409,7 @@ public async Task> QuerySong(int uid, int api, int sid = -1, { var result = await context.Charts .Include(c => c.Song) + .AsSplitQuery() .AsNoTracking() .FirstAsync(c => c.ChartId == cid); @@ -384,7 +419,7 @@ public async Task> QuerySong(int uid, int api, int sid = -1, resp.Data.Add(new SongInfo { - Artist = song.Artist, + Artist = org == 0 ? song.Artist : song.OriginalArtist, Bpm = song.Bpm, Cover = song.Cover, Length = song.Length, @@ -425,8 +460,7 @@ public async Task> QuerySong(int uid, int api, int sid = -1, public async Task GetDownload(int uid, int api, int cid) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server does not support api version {api}."); + Util.CheckVersion(api); var resp = new DownloadResponse(); Chart chart = null; @@ -436,6 +470,7 @@ public async Task GetDownload(int uid, int api, int cid) { chart = await context.Charts .Include(c => c.Song) + .AsSplitQuery() .AsNoTracking() .FirstAsync(c => c.ChartId == cid); } @@ -450,10 +485,11 @@ public async Task GetDownload(int uid, int api, int cid) // Try to find the download records with chart. try { - var dls = context.Downloads + var dls = await context.Downloads .Include(d => d.Chart.Song) + .AsSplitQuery() .AsNoTracking() - .ToList(); + .ToListAsync(); if (dls.Any()) { @@ -498,8 +534,7 @@ public async Task GetDownload(int uid, int api, int cid) public async Task> GetEvents(int uid, int api, int active, int from) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server does not support api version {api}."); + Util.CheckVersion(api); var maxItem = 50; var resp = new Response(); @@ -514,7 +549,7 @@ public async Task> GetEvents(int uid, int api, int active, i query = query.Where(e => e.Active); // Success. - var result = query.ToList(); // TODO: Save events to cache? + var result = await query.ToListAsync(); // TODO: Save events to cache? resp.Code = 0; // To see if has more to send. @@ -567,8 +602,7 @@ public async Task> GetEvents(int uid, int api, int active, i public async Task> GetEvent(int uid, int api, int eid, int org, int from) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server does not support api version {api}."); + Util.CheckVersion(api); var maxItem = 50; // Max item server will return. var resp = new Response(); @@ -578,6 +612,7 @@ public async Task> GetEvent(int uid, int api, int eid, // Try to find event with eid. var @event = await context.Events .Include(e => e.EventCharts) + .AsSplitQuery() .FirstAsync(e => e.EventId == eid); // TODO: Save event to cache? // success. @@ -603,7 +638,7 @@ public async Task> GetEvent(int uid, int api, int eid, var chart = charts[i].Chart; resp.Data.Add(new EventChartInfo { - Artist = song.Artist, + Artist = org == 0 ? song.Artist : song.OriginalArtist, Cid = chart.ChartId, Cover = song.Cover, Creator = chart.Creator, diff --git a/MorePracticeMalodyServer/Controllers/UploadController.cs b/MorePracticeMalodyServer/Controllers/UploadController.cs index 5578d6d..8ea6878 100644 --- a/MorePracticeMalodyServer/Controllers/UploadController.cs +++ b/MorePracticeMalodyServer/Controllers/UploadController.cs @@ -78,8 +78,8 @@ public async Task PostSign(int uid, int api, [FromForm] int sid, [ [FromForm] string name, [FromForm] string hash) { // If not support the api version, throw a exception. - if (api != Consts.API_VERSION) - throw new NotSupportedException($"This server does not support api version {api}."); + Util.CheckVersion(api); + logger.LogInformation("Upload sign phase!"); logger.LogInformation("User {uid} trying to upload chart {cid} for song {sid}.", uid, cid, sid); @@ -181,6 +181,8 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro [FromForm] string name, [FromForm] string hash, [FromForm] int size, [FromForm] string main) { + Util.CheckVersion(api); + var selfProvide = true; // Check if chart exist. @@ -205,14 +207,18 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro if (!chart.Downloads.Any()) { for (var i = 0; i != names.Length; i++) + { + var encodedName = HttpUtility.UrlEncode(names[i]); + context.Downloads.Add(new Download { ChartId = cid, File = - $"http://{Request.Host.Value}/{sid}/{cid}/{HttpUtility.UrlEncode(names[i])}", // Fix issue #1 + $"http://{Request.Host.Value}/{sid}/{cid}/{encodedName}", // Fix issue #1 Hash = hashes[i], - Name = HttpUtility.UrlEncode(names[i]) + Name = encodedName }); + } } else { @@ -226,14 +232,18 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro .ToList(); foreach (var add in adds) // Add new files. + { + var encodedName = HttpUtility.UrlEncode(add); + context.Downloads.Add(new Download { ChartId = cid, File = - $"http://{Request.Host.Value}/{sid}/{cid}/{HttpUtility.UrlEncode(add)}", // Fix issue #1 - Hash = nameToHash[add], - Name = HttpUtility.UrlEncode(add) + $"http://{Request.Host.Value}/{sid}/{cid}/{encodedName}", // Fix issue #1 + Hash = nameToHash[encodedName], + Name = encodedName }); + } // Find what should delete. var dels = chart.Downloads.Select(d => HttpUtility.UrlDecode(d.Name)) // Fix issue #1 @@ -243,7 +253,7 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro chart.Downloads.RemoveAll(d => d.Name == HttpUtility.UrlEncode(del)); // Fix issue #1 //Update others. - foreach (var d in chart.Downloads) d.Hash = nameToHash[d.Name]; + foreach (var d in chart.Downloads) d.Hash = nameToHash[HttpUtility.UrlDecode(d.Name)]; } await context.SaveChangesAsync(); @@ -263,7 +273,7 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro "wwwroot", sid.ToString(), cid.ToString(), HttpUtility.UrlEncode(hashToName[main])), - HttpUtility.UrlEncode(hashToName[main]))); // Fix issue #1 + hashToName[main])); // Fix issue #1 var chartMeta = file.Meta; var songMeta = file.Meta.Song; @@ -288,9 +298,16 @@ public async Task FinishCheck(int uid, int api, [FromForm] int sid, [Fro $"http://{Request.Host.Value}/{sid}/{cid}/{HttpUtility.UrlEncode(chartMeta.Background)}"; // Fix issue #1 chart.Song.Length = 0; // See above. chart.Song.Mode |= 1 << chartMeta.Mode; + chart.Song.OriginalArtist = songMeta.Artistorg ?? songMeta.Artist; chart.Song.OriginalTitle = songMeta.Titleorg ?? songMeta.Title; chart.Song.Title = songMeta.Title; + // Prepare search string for searching. + chart.Song.SearchString = + $"{Util.TrimSpecial(songMeta.Artist).ToLower()}-{Util.TrimSpecial(songMeta.Title).ToLower()}"; + chart.Song.OriginalSearchString = + $"{Util.TrimSpecial(chart.Song.OriginalArtist).ToLower()}-{Util.TrimSpecial(chart.Song.OriginalTitle).ToLower()}"; + await context.SaveChangesAsync(); } catch (FileNotFoundException) // WHY?? diff --git a/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.Designer.cs b/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.Designer.cs new file mode 100644 index 0000000..1ee69c5 --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.Designer.cs @@ -0,0 +1,252 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MorePracticeMalodyServer.Data; + +namespace MorePracticeMalodyServer.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20210824122852_DeleteUniqueOnDownloadHash")] + partial class DeleteUniqueOnDownloadHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.9"); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Property("ChartId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Creator") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("ChartId"); + + b.HasIndex("SongId"); + + b.HasIndex("Type", "Mode", "Level"); + + b.ToTable("Charts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Hash") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.ToTable("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("End") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("EventId"); + + b.HasIndex("Active"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.HasIndex("EventId"); + + b.ToTable("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SongId"); + + b.ToTable("Promotions"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Property("SongId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Artist") + .HasColumnType("TEXT"); + + b.Property("Bpm") + .HasColumnType("REAL"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("SongId"); + + b.HasIndex("Title", "Artist", "Mode"); + + b.ToTable("Songs"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany("Charts") + .HasForeignKey("SongId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany("Downloads") + .HasForeignKey("ChartId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany() + .HasForeignKey("ChartId"); + + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Event", null) + .WithMany("EventCharts") + .HasForeignKey("EventId"); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany() + .HasForeignKey("SongId"); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Navigation("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Navigation("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Navigation("Charts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.cs b/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.cs new file mode 100644 index 0000000..bd94a5e --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824122852_DeleteUniqueOnDownloadHash.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MorePracticeMalodyServer.Migrations +{ + public partial class DeleteUniqueOnDownloadHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Downloads_Hash", + table: "Downloads"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Downloads_Hash", + table: "Downloads", + column: "Hash", + unique: true); + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.Designer.cs b/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.Designer.cs new file mode 100644 index 0000000..23280e3 --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.Designer.cs @@ -0,0 +1,269 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MorePracticeMalodyServer.Data; + +namespace MorePracticeMalodyServer.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20210824134819_UpdateSearch")] + partial class UpdateSearch + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.9"); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Property("ChartId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Creator") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("ChartId"); + + b.HasIndex("Level"); + + b.HasIndex("Mode"); + + b.HasIndex("SongId"); + + b.HasIndex("Type"); + + b.ToTable("Charts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Hash") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.ToTable("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("End") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("EventId"); + + b.HasIndex("Active"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.HasIndex("EventId"); + + b.ToTable("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SongId"); + + b.ToTable("Promotions"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Property("SongId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Artist") + .HasColumnType("TEXT"); + + b.Property("Bpm") + .HasColumnType("REAL"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("OriginalArtist") + .HasColumnType("TEXT"); + + b.Property("OriginalSearchString") + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("SearchSting") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("SongId"); + + b.HasIndex("Mode"); + + b.HasIndex("OriginalSearchString"); + + b.HasIndex("SearchSting"); + + b.ToTable("Songs"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany("Charts") + .HasForeignKey("SongId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany("Downloads") + .HasForeignKey("ChartId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany() + .HasForeignKey("ChartId"); + + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Event", null) + .WithMany("EventCharts") + .HasForeignKey("EventId"); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany() + .HasForeignKey("SongId"); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Navigation("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Navigation("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Navigation("Charts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.cs b/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.cs new file mode 100644 index 0000000..b332389 --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824134819_UpdateSearch.cs @@ -0,0 +1,115 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MorePracticeMalodyServer.Migrations +{ + public partial class UpdateSearch : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Songs_Title_Artist_Mode", + table: "Songs"); + + migrationBuilder.DropIndex( + name: "IX_Charts_Type_Mode_Level", + table: "Charts"); + + migrationBuilder.AddColumn( + name: "OriginalArtist", + table: "Songs", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "OriginalSearchString", + table: "Songs", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "SearchSting", + table: "Songs", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Songs_Mode", + table: "Songs", + column: "Mode"); + + migrationBuilder.CreateIndex( + name: "IX_Songs_OriginalSearchString", + table: "Songs", + column: "OriginalSearchString"); + + migrationBuilder.CreateIndex( + name: "IX_Songs_SearchSting", + table: "Songs", + column: "SearchSting"); + + migrationBuilder.CreateIndex( + name: "IX_Charts_Level", + table: "Charts", + column: "Level"); + + migrationBuilder.CreateIndex( + name: "IX_Charts_Mode", + table: "Charts", + column: "Mode"); + + migrationBuilder.CreateIndex( + name: "IX_Charts_Type", + table: "Charts", + column: "Type"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Songs_Mode", + table: "Songs"); + + migrationBuilder.DropIndex( + name: "IX_Songs_OriginalSearchString", + table: "Songs"); + + migrationBuilder.DropIndex( + name: "IX_Songs_SearchSting", + table: "Songs"); + + migrationBuilder.DropIndex( + name: "IX_Charts_Level", + table: "Charts"); + + migrationBuilder.DropIndex( + name: "IX_Charts_Mode", + table: "Charts"); + + migrationBuilder.DropIndex( + name: "IX_Charts_Type", + table: "Charts"); + + migrationBuilder.DropColumn( + name: "OriginalArtist", + table: "Songs"); + + migrationBuilder.DropColumn( + name: "OriginalSearchString", + table: "Songs"); + + migrationBuilder.DropColumn( + name: "SearchSting", + table: "Songs"); + + migrationBuilder.CreateIndex( + name: "IX_Songs_Title_Artist_Mode", + table: "Songs", + columns: new[] { "Title", "Artist", "Mode" }); + + migrationBuilder.CreateIndex( + name: "IX_Charts_Type_Mode_Level", + table: "Charts", + columns: new[] { "Type", "Mode", "Level" }); + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.Designer.cs b/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.Designer.cs new file mode 100644 index 0000000..5a323a2 --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.Designer.cs @@ -0,0 +1,269 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MorePracticeMalodyServer.Data; + +namespace MorePracticeMalodyServer.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20210824142931_FixTypo")] + partial class FixTypo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.9"); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Property("ChartId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Creator") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("ChartId"); + + b.HasIndex("Level"); + + b.HasIndex("Mode"); + + b.HasIndex("SongId"); + + b.HasIndex("Type"); + + b.ToTable("Charts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("File") + .HasColumnType("TEXT"); + + b.Property("Hash") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.ToTable("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("End") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("EventId"); + + b.HasIndex("Active"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChartId") + .HasColumnType("INTEGER"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChartId"); + + b.HasIndex("EventId"); + + b.ToTable("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SongId"); + + b.ToTable("Promotions"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Property("SongId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Artist") + .HasColumnType("TEXT"); + + b.Property("Bpm") + .HasColumnType("REAL"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Mode") + .HasColumnType("INTEGER"); + + b.Property("OriginalArtist") + .HasColumnType("TEXT"); + + b.Property("OriginalSearchString") + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("SearchString") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("SongId"); + + b.HasIndex("Mode"); + + b.HasIndex("OriginalSearchString"); + + b.HasIndex("SearchString"); + + b.ToTable("Songs"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany("Charts") + .HasForeignKey("SongId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Download", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany("Downloads") + .HasForeignKey("ChartId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.EventChart", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Chart", "Chart") + .WithMany() + .HasForeignKey("ChartId"); + + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Event", null) + .WithMany("EventCharts") + .HasForeignKey("EventId"); + + b.Navigation("Chart"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Promotion", b => + { + b.HasOne("MorePracticeMalodyServer.Model.DbModel.Song", "Song") + .WithMany() + .HasForeignKey("SongId"); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Chart", b => + { + b.Navigation("Downloads"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Event", b => + { + b.Navigation("EventCharts"); + }); + + modelBuilder.Entity("MorePracticeMalodyServer.Model.DbModel.Song", b => + { + b.Navigation("Charts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.cs b/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.cs new file mode 100644 index 0000000..1075607 --- /dev/null +++ b/MorePracticeMalodyServer/Migrations/20210824142931_FixTypo.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MorePracticeMalodyServer.Migrations +{ + public partial class FixTypo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "SearchSting", + table: "Songs", + newName: "SearchString"); + + migrationBuilder.RenameIndex( + name: "IX_Songs_SearchSting", + table: "Songs", + newName: "IX_Songs_SearchString"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "SearchString", + table: "Songs", + newName: "SearchSting"); + + migrationBuilder.RenameIndex( + name: "IX_Songs_SearchString", + table: "Songs", + newName: "IX_Songs_SearchSting"); + } + } +} diff --git a/MorePracticeMalodyServer/Migrations/DataContextModelSnapshot.cs b/MorePracticeMalodyServer/Migrations/DataContextModelSnapshot.cs index 2b13634..3c2f2ed 100644 --- a/MorePracticeMalodyServer/Migrations/DataContextModelSnapshot.cs +++ b/MorePracticeMalodyServer/Migrations/DataContextModelSnapshot.cs @@ -51,9 +51,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("ChartId"); + b.HasIndex("Level"); + + b.HasIndex("Mode"); + b.HasIndex("SongId"); - b.HasIndex("Type", "Mode", "Level"); + b.HasIndex("Type"); b.ToTable("Charts"); }); @@ -81,9 +85,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ChartId"); - b.HasIndex("Hash") - .IsUnique(); - b.ToTable("Downloads"); }); @@ -173,9 +174,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Mode") .HasColumnType("INTEGER"); + b.Property("OriginalArtist") + .HasColumnType("TEXT"); + + b.Property("OriginalSearchString") + .HasColumnType("TEXT"); + b.Property("OriginalTitle") .HasColumnType("TEXT"); + b.Property("SearchString") + .HasColumnType("TEXT"); + b.Property("Time") .HasColumnType("TEXT"); @@ -184,7 +194,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("SongId"); - b.HasIndex("Title", "Artist", "Mode"); + b.HasIndex("Mode"); + + b.HasIndex("OriginalSearchString"); + + b.HasIndex("SearchString"); b.ToTable("Songs"); }); diff --git a/MorePracticeMalodyServer/Model/DbModel/Chart.cs b/MorePracticeMalodyServer/Model/DbModel/Chart.cs index 2e1a327..64a5eea 100644 --- a/MorePracticeMalodyServer/Model/DbModel/Chart.cs +++ b/MorePracticeMalodyServer/Model/DbModel/Chart.cs @@ -7,7 +7,9 @@ namespace MorePracticeMalodyServer.Model.DbModel /// /// A chart in database. /// - [Index("Type", "Mode", "Level")] + [Index(nameof(Type))] + [Index(nameof(Mode))] + [Index(nameof(Level))] public class Chart { /// diff --git a/MorePracticeMalodyServer/Model/DbModel/Download.cs b/MorePracticeMalodyServer/Model/DbModel/Download.cs index 3c0eda0..bd9e58c 100644 --- a/MorePracticeMalodyServer/Model/DbModel/Download.cs +++ b/MorePracticeMalodyServer/Model/DbModel/Download.cs @@ -8,7 +8,6 @@ namespace MorePracticeMalodyServer.Model.DbModel /// Main data types used for download. /// [Index("Chart")] - [Index(nameof(Hash), IsUnique = true)] public class Download // Unknown types are begin with '¿'. { /// diff --git a/MorePracticeMalodyServer/Model/DbModel/Song.cs b/MorePracticeMalodyServer/Model/DbModel/Song.cs index 2de7f57..945e8d0 100644 --- a/MorePracticeMalodyServer/Model/DbModel/Song.cs +++ b/MorePracticeMalodyServer/Model/DbModel/Song.cs @@ -9,7 +9,9 @@ namespace MorePracticeMalodyServer.Model.DbModel /// /// A song record in the database. /// - [Index("Title", "Artist", "Mode")] + [Index(nameof(SearchString))] + [Index(nameof(OriginalSearchString))] + [Index(nameof(Mode))] public class Song { /// @@ -48,6 +50,21 @@ public class Song /// public string Artist { get; set; } + /// + /// Artist in original language. + /// + public string OriginalArtist { get; set; } + + /// + /// Use to search. + /// + public string SearchString { get; set; } + + /// + /// Use to search in original language. + /// + public string OriginalSearchString { get; set; } + /// /// Mode bitmask of song. /// diff --git a/MorePracticeMalodyServer/MorePracticeMalodyServer.csproj b/MorePracticeMalodyServer/MorePracticeMalodyServer.csproj index 49c75c3..68d976f 100644 --- a/MorePracticeMalodyServer/MorePracticeMalodyServer.csproj +++ b/MorePracticeMalodyServer/MorePracticeMalodyServer.csproj @@ -4,8 +4,8 @@ net5.0 37bf4e05-2836-4dd4-b7e4-01b6b356ce73 Linux - 1.0.0.66 - 1.0.0.66 + 1.0.1.0 + 1.0.1.0 @@ -17,8 +17,8 @@ False SettingsVersion None - IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.Increment - IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.Increment + IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.IncrementWithResetOnIncrease + IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.IncrementWithResetOnIncrease @@ -27,11 +27,11 @@ True True False - IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.Increment + IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.IncrementWithResetOnIncrease False SettingsVersion None - IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.Increment + IncrementOnDemand.IncrementOnDemand.IncrementOnDemand.IncrementWithResetOnIncrease diff --git a/MorePracticeMalodyServer/Startup.cs b/MorePracticeMalodyServer/Startup.cs index 75ee50b..4a12f88 100644 --- a/MorePracticeMalodyServer/Startup.cs +++ b/MorePracticeMalodyServer/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using MorePracticeMalodyServer.Data; namespace MorePracticeMalodyServer @@ -25,7 +26,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContextPool(option => { #if DEBUG - option.LogTo(Console.WriteLine) + option.LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging(); #endif if (string.IsNullOrWhiteSpace(Configuration["Data:ConnectionString"])) diff --git a/MorePracticeMalodyServer/Util.cs b/MorePracticeMalodyServer/Util.cs new file mode 100644 index 0000000..97ddbc9 --- /dev/null +++ b/MorePracticeMalodyServer/Util.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; + +namespace MorePracticeMalodyServer +{ + public class Util + { + public static void CheckVersion(int version) + { + if (version > Consts.API_VERSION) + throw new NotSupportedException($"This server does not support api version {version}."); + } + + public static bool IsCharacter(char c) + { + // Japanese character All Chinese character + return c is (>= '\u3041' and <= '\u30FE') or (>= '\u3400' and <= '\uFA29'); + } + + public static string TrimSpecial(string input) + { + var charArray = input.Where(c => IsCharacter(c) || char.IsLetterOrDigit(c)).ToArray(); + return new string(charArray); + } + } +} \ No newline at end of file diff --git a/MorePracticeMalodyServer/data.sqlite b/MorePracticeMalodyServer/data.sqlite index 0f9b846..d3a6512 100644 Binary files a/MorePracticeMalodyServer/data.sqlite and b/MorePracticeMalodyServer/data.sqlite differ diff --git a/README.md b/README.md index ffbcdfd..63dbc04 100644 --- a/README.md +++ b/README.md @@ -29,19 +29,19 @@ You can use it as you download, no need configuration, and it can run the server * Charts upload/download * Charts Searching (using Keywords or id) * Chart list +* Promotion list * Saves file on local server * Event list * Supports MySql/SQLServer/sqlite **Currently Known Bugs:** -* Charts searching: - * Results are not returned when the keyword is at the beginning. This is a bug of EF Core. You can [track this bug](https://github.com/dotnet/efcore/issues/25644) if you have interests. +* All right :) + **Features Under Construction:** -* Promotion list +* Supports api version 202108 **Will be Added:** -* Promotion list * Upload identity restructions * User identity restructions * Save on third-party device or website diff --git a/README.zh.md b/README.zh.md index d960818..7ccc4f0 100644 --- a/README.zh.md +++ b/README.zh.md @@ -29,19 +29,18 @@ API版本:**202103** * 谱面上传/下载 * 谱面搜索(关键字/按id) * 谱面列表 +* 推荐列表 * 自提供存储 * 活动列表 * MySql/SQLServer/sqlite支持 **目前已知有bug的功能:** -* 谱面搜索: - * 当关键字位于开头时不会返回结果。这是EF Core的bug。如果有兴趣,可以[在此追踪](https://github.com/dotnet/efcore/issues/25644)。 +* 一切正常 :) **目前未实现的功能:** -* 推荐列表 +* 支持 202108 API版本 **将在未来添加的功能:** -* 推荐列表 * 上传身份限制 * 用户身份限制 * 第三方存储提供