mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-16 10:11:28 +00:00
Merge branch 'main' into patch-1
This commit is contained in:
commit
38e1771cc2
48 changed files with 636 additions and 23 deletions
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>De seneste billeder</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Neueste Fotos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent maps</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Plej lastatempaj bildoj</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Fotos más recientes</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Fotos más recientes</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Viimeisimmät kuvat</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>最近の写真</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Siste bilder</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Ostatnie zdjęcia</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Fotos mais recentes</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Самые последние фото</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Senaste foton</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -53,4 +53,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -54,4 +54,7 @@
|
|||
<data name="recent_photos" xml:space="preserve">
|
||||
<value>Most recent photos</value>
|
||||
</data>
|
||||
<data name="email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
</root>
|
|
@ -4,6 +4,7 @@ public static class GeneralStrings
|
|||
{
|
||||
public static readonly TranslatableString Username = create("username");
|
||||
public static readonly TranslatableString Password = create("password");
|
||||
public static readonly TranslatableString Email = create("email");
|
||||
public static readonly TranslatableString Register = create("register");
|
||||
public static readonly TranslatableString ResetPassword = create("reset_password");
|
||||
public static readonly TranslatableString ForgotPassword = create("forgot_password");
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#nullable enable
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Levels.Categories;
|
||||
|
@ -6,8 +8,8 @@ using LBPUnion.ProjectLighthouse.Logging;
|
|||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
|
@ -23,8 +25,128 @@ public class CollectionController : ControllerBase
|
|||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("playlists/{playlistId:int}/slots")]
|
||||
public async Task<IActionResult> GetPlaylistSlots(int playlistId)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
|
||||
if (targetPlaylist == null) return this.BadRequest();
|
||||
|
||||
IQueryable<Slot> slots = this.database.Slots.Include(s => s.Creator)
|
||||
.Include(s => s.Location)
|
||||
.Where(s => targetPlaylist.SlotIds.Contains(s.SlotId));
|
||||
|
||||
string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize());
|
||||
int total = targetPlaylist.SlotIds.Length;
|
||||
|
||||
return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", total));
|
||||
}
|
||||
|
||||
[HttpPost("playlists/{playlistId:int}")]
|
||||
[HttpPost("playlists/{playlistId:int}/slots")]
|
||||
[HttpPost("playlists/{playlistId:int}/slots/{slotId:int}/delete")]
|
||||
[HttpPost("playlists/{playlistId:int}/order_slots")]
|
||||
public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
|
||||
if (targetPlaylist == null) return this.BadRequest();
|
||||
|
||||
if (token.UserId != targetPlaylist.CreatorId) return this.BadRequest();
|
||||
|
||||
// Delete a slot from playlist
|
||||
if (slotId != 0)
|
||||
{
|
||||
targetPlaylist.SlotIds = targetPlaylist.SlotIds.Where(s => s != slotId).ToArray();
|
||||
await this.database.SaveChangesAsync();
|
||||
return this.Ok(this.GetUserPlaylists(token.UserId));
|
||||
}
|
||||
|
||||
Playlist? newPlaylist = await this.getPlaylistFromBody();
|
||||
|
||||
if (newPlaylist == null) return this.BadRequest();
|
||||
|
||||
if (newPlaylist.LevelIds != null)
|
||||
{
|
||||
HashSet<int> slotIds = new(targetPlaylist.SlotIds);
|
||||
|
||||
// Reorder
|
||||
if (slotIds.SetEquals(newPlaylist.LevelIds))
|
||||
{
|
||||
targetPlaylist.SlotIds = newPlaylist.LevelIds;
|
||||
}
|
||||
// Add a level
|
||||
else
|
||||
{
|
||||
foreach (int id in newPlaylist.LevelIds)
|
||||
{
|
||||
targetPlaylist.SlotIds = targetPlaylist.SlotIds.Append(id).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newPlaylist.Name)) targetPlaylist.Name = newPlaylist.Name;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newPlaylist.Description)) targetPlaylist.Description = newPlaylist.Description;
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok(this.GetUserPlaylists(token.UserId));
|
||||
}
|
||||
|
||||
private string GetUserPlaylists(int userId)
|
||||
{
|
||||
string response = Enumerable.Aggregate(
|
||||
this.database.Playlists.Include(p => p.Creator).Where(p => p.CreatorId == userId),
|
||||
string.Empty,
|
||||
(current, slot) => current + slot.Serialize());
|
||||
int total = this.database.Playlists.Count(p => p.CreatorId == userId);
|
||||
|
||||
return LbpSerializer.TaggedStringElement("playlists", response, new Dictionary<string, object>
|
||||
{
|
||||
{"total", total},
|
||||
{"hint_start", total+1},
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("playlists")]
|
||||
public async Task<IActionResult> CreatePlaylist()
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId);
|
||||
|
||||
if (playlistCount > ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) return this.BadRequest();
|
||||
|
||||
Playlist? playlist = await this.getPlaylistFromBody();
|
||||
|
||||
if (playlist == null) return this.BadRequest();
|
||||
|
||||
playlist.CreatorId = token.UserId;
|
||||
|
||||
this.database.Playlists.Add(playlist);
|
||||
|
||||
await this.database.SaveChangesAsync();
|
||||
|
||||
return this.Ok(this.GetUserPlaylists(token.UserId));
|
||||
}
|
||||
|
||||
[HttpGet("user/{username}/playlists")]
|
||||
public IActionResult GetUserPlaylists(string username) => this.Ok("<playlists></playlists>");
|
||||
public async Task<IActionResult> GetUserPlaylists(string username)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
|
||||
if (targetUserId == 0) return this.BadRequest();
|
||||
|
||||
return this.Ok(this.GetUserPlaylists(targetUserId));
|
||||
}
|
||||
|
||||
[HttpGet("searches")]
|
||||
[HttpGet("genres")]
|
||||
|
@ -121,4 +243,19 @@ public class CollectionController : ControllerBase
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<Playlist?> getPlaylistFromBody()
|
||||
{
|
||||
this.Request.Body.Position = 0;
|
||||
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
|
||||
|
||||
string rootElement = bodyString.StartsWith("<playlist>") ? "playlist" : "levels";
|
||||
XmlSerializer serializer = new(typeof(Playlist), new XmlRootAttribute(rootElement));
|
||||
Playlist? playlist = (Playlist?)serializer.Deserialize(new StringReader(bodyString));
|
||||
|
||||
SanitizationHelper.SanitizeStringsInClass(playlist);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
}
|
|
@ -197,6 +197,64 @@ public class ListController : ControllerBase
|
|||
|
||||
#endregion
|
||||
|
||||
#region Hearted Playlists
|
||||
|
||||
[HttpGet("favouritePlaylists/{username}")]
|
||||
public async Task<IActionResult> GetFavouritePlaylists(string username, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
|
||||
int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
|
||||
if (targetUserId == 0) return this.StatusCode(403, "");
|
||||
|
||||
IEnumerable<Playlist> heartedPlaylists = this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId)
|
||||
.Include(p => p.Playlist).Include(p => p.Playlist.Creator).OrderByDescending(p => p.HeartedPlaylistId).Select(p => p.Playlist);
|
||||
|
||||
string response = heartedPlaylists.Aggregate(string.Empty, (current, p) => current + p.Serialize());
|
||||
|
||||
return this.Ok
|
||||
(
|
||||
LbpSerializer.TaggedStringElement("favouritePlaylists", response, new Dictionary<string, object>
|
||||
{
|
||||
{ "total", this.database.HeartedPlaylists.Count(p => p.UserId == targetUserId) },
|
||||
{ "hint_start", pageStart + Math.Min(pageSize, 30) },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("favourite/playlist/{playlistId:int}")]
|
||||
public async Task<IActionResult> AddFavouritePlaylist(int playlistId)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
|
||||
if (playlist == null) return this.NotFound();
|
||||
|
||||
await this.database.HeartPlaylist(token.UserId, playlist);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
[HttpPost("unfavourite/playlist/{playlistId:int}")]
|
||||
public async Task<IActionResult> RemoveFavouritePlaylist(int playlistId)
|
||||
{
|
||||
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
|
||||
if (token == null) return this.StatusCode(403, "");
|
||||
|
||||
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
|
||||
if (playlist == null) return this.NotFound();
|
||||
|
||||
await this.database.UnheartPlaylist(token.UserId, playlist);
|
||||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion Levels
|
||||
|
||||
#region Users
|
||||
|
|
|
@ -41,9 +41,12 @@
|
|||
<input type="hidden" id="redirect" name="redirect">
|
||||
|
||||
<div class="field">
|
||||
<label>@Model.Translate(GeneralStrings.Username)</label>
|
||||
@{
|
||||
string username = ServerConfiguration.Instance.Mail.MailEnabled ? Model.Translate(GeneralStrings.Email) : Model.Translate(GeneralStrings.Username);
|
||||
}
|
||||
<label>@username</label>
|
||||
<div class="ui left icon input">
|
||||
<input type="text" name="username" id="text" placeholder="@Model.Translate(GeneralStrings.Username)">
|
||||
<input type="text" name="username" id="text" placeholder="@username">
|
||||
<i class="user icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,9 +78,9 @@
|
|||
}
|
||||
</div>
|
||||
<br/>
|
||||
<a href="/passwordResetRequest">
|
||||
<div class="ui button">
|
||||
@Model.Translate(GeneralStrings.ForgotPassword)
|
||||
</div>
|
||||
</a>
|
||||
<a href="/passwordResetRequest">
|
||||
<div class="ui button">
|
||||
@Model.Translate(GeneralStrings.ForgotPassword)
|
||||
</div>
|
||||
</a>
|
||||
</form>
|
|
@ -27,7 +27,7 @@ public class LoginForm : BaseLayout
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
this.Error = this.Translate(ErrorStrings.UsernameInvalid);
|
||||
this.Error = ServerConfiguration.Instance.Mail.MailEnabled ? this.Translate(ErrorStrings.UsernameInvalid) : this.Translate(ErrorStrings.EmailInvalid);
|
||||
return this.Page();
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,23 @@ public class LoginForm : BaseLayout
|
|||
return this.Page();
|
||||
}
|
||||
|
||||
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
User? user;
|
||||
|
||||
if (!ServerConfiguration.Instance.Mail.MailEnabled)
|
||||
{
|
||||
user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = await this.Database.Users.FirstOrDefaultAsync(u => u.EmailAddress == username);
|
||||
if (user == null)
|
||||
{
|
||||
User? noEmailUser = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (noEmailUser != null && noEmailUser.EmailAddress == null) user = noEmailUser;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
Logger.Warn($"User {username} failed to login on web due to invalid username", LogArea.Login);
|
||||
|
|
|
@ -12,7 +12,7 @@ public class PirateSignupPage : BaseLayout
|
|||
public IActionResult OnGet()
|
||||
{
|
||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||
if (user == null) return this.RedirectToPage("/login");
|
||||
if (user == null) return this.Redirect("/login");
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
|
|
@ -74,6 +74,12 @@
|
|||
<i class="lock icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="age-checkbox" required>
|
||||
<label for="age-checkbox">I confirm that I am above 13 years old</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||
{
|
||||
|
|
|
@ -67,7 +67,7 @@ function onSubmit(e){
|
|||
@if (ServerConfiguration.Instance.Mail.MailEnabled && (Model.User == Model.ProfileUser || Model.User!.IsAdmin))
|
||||
{
|
||||
<div class="field">
|
||||
<label style="text-align: left" for="email">Email</label>
|
||||
<label style="text-align: left" for="email">@Model.Translate(GeneralStrings.Email)</label>
|
||||
<input type="text" name="email" id="email" required value="@Model.ProfileUser.EmailAddress" placeholder="Email Address">
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.5.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="105.0.5195.5200" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
|
|
|
@ -28,6 +28,8 @@ public class RegisterTests : LighthouseWebTest
|
|||
this.Driver.FindElement(By.Id("password")).SendKeys(password);
|
||||
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password);
|
||||
|
||||
this.Driver.FindElement(By.Id("age-checkbox")).Click();
|
||||
|
||||
this.Driver.FindElement(By.Id("submit")).Click();
|
||||
|
||||
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
|
@ -51,6 +53,8 @@ public class RegisterTests : LighthouseWebTest
|
|||
this.Driver.FindElement(By.Id("password")).SendKeys(password);
|
||||
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password + "a");
|
||||
|
||||
this.Driver.FindElement(By.Id("age-checkbox")).Click();
|
||||
|
||||
this.Driver.FindElement(By.Id("submit")).Click();
|
||||
|
||||
User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
|
@ -76,6 +80,8 @@ public class RegisterTests : LighthouseWebTest
|
|||
this.Driver.FindElement(By.Id("password")).SendKeys(password);
|
||||
this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password);
|
||||
|
||||
this.Driver.FindElement(By.Id("age-checkbox")).Click();
|
||||
|
||||
this.Driver.FindElement(By.Id("submit")).Click();
|
||||
|
||||
Assert.Contains("The username you've chosen is already taken.", this.Driver.PageSource);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -30,6 +30,7 @@ public class Database : DbContext
|
|||
public DbSet<QueuedLevel> QueuedLevels { get; set; }
|
||||
public DbSet<HeartedLevel> HeartedLevels { get; set; }
|
||||
public DbSet<HeartedProfile> HeartedProfiles { get; set; }
|
||||
public DbSet<HeartedPlaylist> HeartedPlaylists { get; set; }
|
||||
public DbSet<Comment> Comments { get; set; }
|
||||
public DbSet<GameToken> GameTokens { get; set; }
|
||||
public DbSet<WebToken> WebTokens { get; set; }
|
||||
|
@ -51,6 +52,7 @@ public class Database : DbContext
|
|||
public DbSet<PasswordResetToken> PasswordResetTokens { get; set; }
|
||||
public DbSet<RegistrationToken> RegistrationTokens { get; set; }
|
||||
public DbSet<APIKey> APIKeys { get; set; }
|
||||
public DbSet<Playlist> Playlists { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
|
||||
|
@ -231,6 +233,28 @@ public class Database : DbContext
|
|||
await this.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task HeartPlaylist(int userId, Playlist heartedPlaylist)
|
||||
{
|
||||
HeartedPlaylist? heartedList = await this.HeartedPlaylists.FirstOrDefaultAsync(p => p.UserId == userId && p.PlaylistId == heartedPlaylist.PlaylistId);
|
||||
if (heartedList != null) return;
|
||||
|
||||
this.HeartedPlaylists.Add(new HeartedPlaylist()
|
||||
{
|
||||
PlaylistId = heartedPlaylist.PlaylistId,
|
||||
UserId = userId,
|
||||
});
|
||||
|
||||
await this.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UnheartPlaylist(int userId, Playlist heartedPlaylist)
|
||||
{
|
||||
HeartedPlaylist? heartedList = await this.HeartedPlaylists.FirstOrDefaultAsync(p => p.UserId == userId && p.PlaylistId == heartedPlaylist.PlaylistId);
|
||||
if (heartedList != null) this.HeartedPlaylists.Remove(heartedList);
|
||||
|
||||
await this.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task HeartLevel(int userId, Slot heartedSlot)
|
||||
{
|
||||
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == userId && q.SlotId == heartedSlot.SlotId);
|
||||
|
|
21
ProjectLighthouse/Levels/HeartedPlaylist.cs
Normal file
21
ProjectLighthouse/Levels/HeartedPlaylist.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Levels;
|
||||
|
||||
public class HeartedPlaylist
|
||||
{
|
||||
[Key]
|
||||
public int HeartedPlaylistId { get; set; }
|
||||
|
||||
public int UserId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public User User { get; set; }
|
||||
|
||||
public int PlaylistId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(PlaylistId))]
|
||||
public Playlist Playlist { get; set; }
|
||||
}
|
70
ProjectLighthouse/Levels/Playlist.cs
Normal file
70
ProjectLighthouse/Levels/Playlist.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||
using LBPUnion.ProjectLighthouse.Serialization;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Levels;
|
||||
|
||||
public class Playlist
|
||||
{
|
||||
[Key]
|
||||
public int PlaylistId { get; set; }
|
||||
|
||||
[XmlElement("name")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[XmlElement("description")]
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
public int CreatorId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(CreatorId))]
|
||||
[JsonIgnore]
|
||||
public User? Creator { get; set; }
|
||||
|
||||
public int Hearts(Database database) => database.HeartedPlaylists.Count(p => p.HeartedPlaylistId == this.PlaylistId);
|
||||
|
||||
public string SlotCollection { get; set; } = "";
|
||||
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
[XmlElement("level_id")]
|
||||
public int[]? LevelIds { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public int[] SlotIds
|
||||
{
|
||||
get => this.SlotCollection.Split(",").Where(x => int.TryParse(x, out _)).Select(int.Parse).ToArray();
|
||||
set => this.SlotCollection = string.Join(",", value);
|
||||
}
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
using Database database = new();
|
||||
string playlist = LbpSerializer.StringElement("id", this.PlaylistId) +
|
||||
LbpSerializer.StringElement("author",
|
||||
LbpSerializer.StringElement("npHandle", this.Creator?.Username)) +
|
||||
LbpSerializer.StringElement("name", this.Name) +
|
||||
LbpSerializer.StringElement("description", this.Description) +
|
||||
LbpSerializer.StringElement("levels", this.SlotIds.Length) +
|
||||
LbpSerializer.StringElement("hearts", this.Hearts(database)) +
|
||||
LbpSerializer.StringElement("levels_quota", ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) +
|
||||
this.SerializeIcons(database);
|
||||
|
||||
return LbpSerializer.StringElement("playlist", playlist);
|
||||
}
|
||||
|
||||
private string SerializeIcons(Database database)
|
||||
{
|
||||
string iconList = this.SlotIds.Select(id => database.Slots.FirstOrDefault(s => s.SlotId == id))
|
||||
.Where(slot => slot != null && slot.IconHash.Length > 0)
|
||||
.Aggregate(string.Empty, (current, slot) => current + LbpSerializer.StringElement("icon", slot?.IconHash));
|
||||
return LbpSerializer.StringElement("icons", iconList);
|
||||
}
|
||||
|
||||
}
|
94
ProjectLighthouse/Migrations/20220923042831_AddPlaylists.cs
Normal file
94
ProjectLighthouse/Migrations/20220923042831_AddPlaylists.cs
Normal file
|
@ -0,0 +1,94 @@
|
|||
using LBPUnion.ProjectLighthouse;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ProjectLighthouse.Migrations
|
||||
{
|
||||
[DbContext(typeof(Database))]
|
||||
[Migration("20220923042831_AddPlaylists")]
|
||||
public partial class AddPlaylists : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Playlists",
|
||||
columns: table => new
|
||||
{
|
||||
PlaylistId = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Description = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatorId = table.Column<int>(type: "int", nullable: false),
|
||||
SlotCollection = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Playlists", x => x.PlaylistId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Playlists_Users_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "HeartedPlaylists",
|
||||
columns: table => new
|
||||
{
|
||||
HeartedPlaylistId = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
UserId = table.Column<int>(type: "int", nullable: false),
|
||||
PlaylistId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_HeartedPlaylists", x => x.HeartedPlaylistId);
|
||||
table.ForeignKey(
|
||||
name: "FK_HeartedPlaylists_Playlists_PlaylistId",
|
||||
column: x => x.PlaylistId,
|
||||
principalTable: "Playlists",
|
||||
principalColumn: "PlaylistId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_HeartedPlaylists_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_HeartedPlaylists_PlaylistId",
|
||||
table: "HeartedPlaylists",
|
||||
column: "PlaylistId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_HeartedPlaylists_UserId",
|
||||
table: "HeartedPlaylists",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Playlists_CreatorId",
|
||||
table: "Playlists",
|
||||
column: "CreatorId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "HeartedPlaylists");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Playlists");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ public class User
|
|||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int Lists => 0;
|
||||
public int Lists => this.database.Playlists.Count(p => p.CreatorId == this.UserId);
|
||||
|
||||
/// <summary>
|
||||
/// A user-customizable biography shown on the profile card
|
||||
|
@ -116,6 +116,10 @@ public class User
|
|||
[JsonIgnore]
|
||||
public int HeartedUsers => this.database.HeartedProfiles.Count(p => p.UserId == this.UserId);
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int HeartedPlaylists => this.database.HeartedPlaylists.Count(p => p.UserId == this.UserId);
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public int QueuedLevels => this.database.QueuedLevels.Count(p => p.UserId == this.UserId);
|
||||
|
@ -197,14 +201,14 @@ public class User
|
|||
string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +
|
||||
LbpSerializer.StringElement("game", (int)gameVersion) +
|
||||
this.serializeSlots(gameVersion) +
|
||||
LbpSerializer.StringElement<string>("lists", this.Lists, true) +
|
||||
LbpSerializer.StringElement<string>
|
||||
LbpSerializer.StringElement<int>("lists", this.Lists, true) +
|
||||
LbpSerializer.StringElement<int>
|
||||
(
|
||||
"lists_quota",
|
||||
ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota,
|
||||
true
|
||||
) + // technically not a part of the user but LBP expects it
|
||||
LbpSerializer.StringElement<string>("heartCount", this.Hearts, true) +
|
||||
LbpSerializer.StringElement<int>("heartCount", this.Hearts, true) +
|
||||
this.serializeEarth(gameVersion) +
|
||||
LbpSerializer.StringElement<string>("yay2", this.YayHash, true) +
|
||||
LbpSerializer.StringElement<string>("boo2", this.BooHash, true) +
|
||||
|
@ -218,6 +222,7 @@ public class User
|
|||
LbpSerializer.StringElement("location", this.Location.Serialize()) +
|
||||
LbpSerializer.StringElement<int>("favouriteSlotCount", this.HeartedLevels, true) +
|
||||
LbpSerializer.StringElement<int>("favouriteUserCount", this.HeartedUsers, true) +
|
||||
LbpSerializer.StringElement<int>("favouritePlaylistCount", this.HeartedPlaylists, true) +
|
||||
LbpSerializer.StringElement<int>("lolcatftwCount", this.QueuedLevels, true) +
|
||||
LbpSerializer.StringElement<string>("pins", this.Pins, true);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<PackageReference Include="Pfim" Version="0.11.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="Discord.Net.Webhook" Version="3.8.1" />
|
||||
<PackageReference Include="InfluxDB.Client" Version="4.5.0" />
|
||||
<PackageReference Include="InfluxDB.Client" Version="4.6.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace ProjectLighthouse.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.8")
|
||||
.HasAnnotation("ProductVersion", "6.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.CompletedMigration", b =>
|
||||
|
@ -172,6 +172,55 @@ namespace ProjectLighthouse.Migrations
|
|||
b.ToTable("HeartedLevels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.HeartedPlaylist", b =>
|
||||
{
|
||||
b.Property<int>("HeartedPlaylistId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PlaylistId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("HeartedPlaylistId");
|
||||
|
||||
b.HasIndex("PlaylistId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("HeartedPlaylists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.Playlist", b =>
|
||||
{
|
||||
b.Property<int>("PlaylistId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("CreatorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("SlotCollection")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("PlaylistId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.ToTable("Playlists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.QueuedLevel", b =>
|
||||
{
|
||||
b.Property<int>("QueuedLevelId")
|
||||
|
@ -273,6 +322,9 @@ namespace ProjectLighthouse.Migrations
|
|||
b.Property<int>("InternalSlotId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsAdventurePlanet")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<long>("LastUpdated")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
|
@ -903,6 +955,9 @@ namespace ProjectLighthouse.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ChildSlotId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("PlayerIdCollection")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
|
@ -989,6 +1044,36 @@ namespace ProjectLighthouse.Migrations
|
|||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.HeartedPlaylist", b =>
|
||||
{
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.Levels.Playlist", "Playlist")
|
||||
.WithMany()
|
||||
.HasForeignKey("PlaylistId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Playlist");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.Playlist", b =>
|
||||
{
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Levels.QueuedLevel", b =>
|
||||
{
|
||||
b.HasOne("LBPUnion.ProjectLighthouse.Levels.Slot", "Slot")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue