mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-18 15:42:26 +00:00
402 lines
No EOL
15 KiB
C#
402 lines
No EOL
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using LBPUnion.ProjectLighthouse.Administration;
|
|
using LBPUnion.ProjectLighthouse.Administration.Reports;
|
|
using LBPUnion.ProjectLighthouse.Configuration;
|
|
using LBPUnion.ProjectLighthouse.Helpers;
|
|
using LBPUnion.ProjectLighthouse.Levels;
|
|
using LBPUnion.ProjectLighthouse.Levels.Categories;
|
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email;
|
|
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
|
|
using LBPUnion.ProjectLighthouse.Tickets;
|
|
using LBPUnion.ProjectLighthouse.Types;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace LBPUnion.ProjectLighthouse;
|
|
|
|
public class Database : DbContext
|
|
{
|
|
public DbSet<CompletedMigration> CompletedMigrations { get; set; }
|
|
public DbSet<User> Users { get; set; }
|
|
public DbSet<Location> Locations { get; set; }
|
|
public DbSet<Slot> Slots { get; set; }
|
|
public DbSet<QueuedLevel> QueuedLevels { get; set; }
|
|
public DbSet<HeartedLevel> HeartedLevels { get; set; }
|
|
public DbSet<HeartedProfile> HeartedProfiles { get; set; }
|
|
public DbSet<Comment> Comments { get; set; }
|
|
public DbSet<GameToken> GameTokens { get; set; }
|
|
public DbSet<WebToken> WebTokens { get; set; }
|
|
public DbSet<Score> Scores { get; set; }
|
|
public DbSet<PhotoSubject> PhotoSubjects { get; set; }
|
|
public DbSet<Photo> Photos { get; set; }
|
|
public DbSet<LastContact> LastContacts { get; set; }
|
|
public DbSet<VisitedLevel> VisitedLevels { get; set; }
|
|
public DbSet<RatedLevel> RatedLevels { get; set; }
|
|
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
|
|
public DbSet<Review> Reviews { get; set; }
|
|
public DbSet<RatedReview> RatedReviews { get; set; }
|
|
public DbSet<UserApprovedIpAddress> UserApprovedIpAddresses { get; set; }
|
|
public DbSet<DatabaseCategory> CustomCategories { get; set; }
|
|
public DbSet<Reaction> Reactions { get; set; }
|
|
public DbSet<GriefReport> Reports { get; set; }
|
|
public DbSet<EmailVerificationToken> EmailVerificationTokens { get; set; }
|
|
public DbSet<EmailSetToken> EmailSetTokens { get; set; }
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
|
=> options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
|
|
|
|
#nullable enable
|
|
public async Task<User> CreateUser(string username, string password, string? emailAddress = null)
|
|
{
|
|
if (!password.StartsWith('$')) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
|
|
|
|
// 16 is PSN max, 3 is PSN minimum
|
|
if (!ServerStatics.IsUnitTesting || !username.StartsWith("unitTestUser"))
|
|
{
|
|
if (username.Length > 16 || username.Length < 3) throw new ArgumentException(nameof(username) + " is either too long or too short");
|
|
|
|
Regex regex = new("^[a-zA-Z0-9_.-]*$");
|
|
|
|
if (!regex.IsMatch(username)) throw new ArgumentException(nameof(username) + " does not match the username regex");
|
|
}
|
|
|
|
User? user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync();
|
|
if (user != null) return user;
|
|
|
|
Location l = new(); // store to get id after submitting
|
|
this.Locations.Add(l); // add to table
|
|
await this.SaveChangesAsync(); // saving to the database returns the id and sets it on this entity
|
|
|
|
user = new User
|
|
{
|
|
Username = username,
|
|
Password = password,
|
|
LocationId = l.Id,
|
|
Biography = "",
|
|
EmailAddress = emailAddress,
|
|
};
|
|
this.Users.Add(user);
|
|
|
|
await this.SaveChangesAsync();
|
|
|
|
if (emailAddress != null && ServerConfiguration.Instance.Mail.MailEnabled)
|
|
{
|
|
string body = "An account for Project Lighthouse has been registered with this email address.\n\n" +
|
|
$"You can login at {ServerConfiguration.Instance.ExternalUrl}.";
|
|
|
|
SMTPHelper.SendEmail(emailAddress, "Project Lighthouse Account Created: " + username, body);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
public async Task<GameToken?> AuthenticateUser(NPTicket npTicket, string userLocation)
|
|
{
|
|
User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username);
|
|
if (user == null) return null;
|
|
|
|
GameToken gameToken = new()
|
|
{
|
|
UserToken = CryptoHelper.GenerateAuthToken(),
|
|
User = user,
|
|
UserId = user.UserId,
|
|
UserLocation = userLocation,
|
|
GameVersion = npTicket.GameVersion,
|
|
Platform = npTicket.Platform,
|
|
};
|
|
|
|
this.GameTokens.Add(gameToken);
|
|
await this.SaveChangesAsync();
|
|
|
|
return gameToken;
|
|
}
|
|
|
|
#region Hearts & Queues
|
|
|
|
public async Task<bool> RateComment(User user, int commentId, int rating)
|
|
{
|
|
Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId);
|
|
|
|
if (comment == null) return false;
|
|
|
|
if (comment.PosterUserId == user.UserId) return false;
|
|
|
|
Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId);
|
|
if (reaction == null)
|
|
{
|
|
Reaction newReaction = new()
|
|
{
|
|
UserId = user.UserId,
|
|
TargetId = commentId,
|
|
Rating = 0,
|
|
};
|
|
this.Reactions.Add(newReaction);
|
|
await this.SaveChangesAsync();
|
|
reaction = newReaction;
|
|
}
|
|
|
|
rating = Math.Clamp(rating, -1, 1);
|
|
|
|
int oldRating = reaction.Rating;
|
|
if (oldRating == rating) return true;
|
|
|
|
reaction.Rating = rating;
|
|
// if rating changed then we count the number of reactions to ensure accuracy
|
|
List<Reaction> reactions = await this.Reactions.Where(c => c.TargetId == commentId).ToListAsync();
|
|
int yay = 0;
|
|
int boo = 0;
|
|
foreach (Reaction r in reactions)
|
|
{
|
|
switch (r.Rating)
|
|
{
|
|
case -1:
|
|
boo++;
|
|
break;
|
|
case 1:
|
|
yay++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
comment.ThumbsDown = boo;
|
|
comment.ThumbsUp = yay;
|
|
await this.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> PostComment(User user, int targetId, CommentType type, string message)
|
|
{
|
|
if (message.Length > 100) return false;
|
|
|
|
if (type == CommentType.Profile)
|
|
{
|
|
User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId);
|
|
if (targetUser == null) return false;
|
|
}
|
|
else
|
|
{
|
|
Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId);
|
|
if (targetSlot == null) return false;
|
|
}
|
|
|
|
this.Comments.Add
|
|
(
|
|
new Comment
|
|
{
|
|
PosterUserId = user.UserId,
|
|
TargetId = targetId,
|
|
Type = type,
|
|
Message = message,
|
|
Timestamp = TimeHelper.UnixTimeMilliseconds(),
|
|
}
|
|
);
|
|
await this.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task HeartUser(User user, User heartedUser)
|
|
{
|
|
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
|
|
if (heartedProfile != null) return;
|
|
|
|
this.HeartedProfiles.Add
|
|
(
|
|
new HeartedProfile
|
|
{
|
|
HeartedUserId = heartedUser.UserId,
|
|
UserId = user.UserId,
|
|
}
|
|
);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task UnheartUser(User user, User heartedUser)
|
|
{
|
|
HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId);
|
|
if (heartedProfile != null) this.HeartedProfiles.Remove(heartedProfile);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task HeartLevel(User user, Slot heartedSlot)
|
|
{
|
|
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
|
|
if (heartedLevel != null) return;
|
|
|
|
this.HeartedLevels.Add
|
|
(
|
|
new HeartedLevel
|
|
{
|
|
SlotId = heartedSlot.SlotId,
|
|
UserId = user.UserId,
|
|
}
|
|
);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task UnheartLevel(User user, Slot heartedSlot)
|
|
{
|
|
HeartedLevel? heartedLevel = await this.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == heartedSlot.SlotId);
|
|
if (heartedLevel != null) this.HeartedLevels.Remove(heartedLevel);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task QueueLevel(User user, Slot queuedSlot)
|
|
{
|
|
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
|
|
if (queuedLevel != null) return;
|
|
|
|
this.QueuedLevels.Add
|
|
(
|
|
new QueuedLevel
|
|
{
|
|
SlotId = queuedSlot.SlotId,
|
|
UserId = user.UserId,
|
|
}
|
|
);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task UnqueueLevel(User user, Slot queuedSlot)
|
|
{
|
|
QueuedLevel? queuedLevel = await this.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == queuedSlot.SlotId);
|
|
if (queuedLevel != null) this.QueuedLevels.Remove(queuedLevel);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Game Token Shenanigans
|
|
|
|
public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)
|
|
{
|
|
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
|
|
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
|
|
|
|
if (token == null) return null;
|
|
if (!allowUnapproved && !token.Approved) return null;
|
|
|
|
return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId);
|
|
}
|
|
|
|
public async Task<User?> UserFromGameToken
|
|
(GameToken gameToken, bool allowUnapproved = false)
|
|
=> await this.UserFromMMAuth(gameToken.UserToken, allowUnapproved);
|
|
|
|
public async Task<User?> UserFromGameRequest(HttpRequest request, bool allowUnapproved = false)
|
|
{
|
|
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
|
|
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
|
|
|
|
return await this.UserFromMMAuth(mmAuth, allowUnapproved);
|
|
}
|
|
|
|
public async Task<GameToken?> GameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
|
|
{
|
|
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
|
|
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
|
|
|
|
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
|
|
|
|
if (token == null) return null;
|
|
if (!allowUnapproved && !token.Approved) return null;
|
|
|
|
return token;
|
|
}
|
|
|
|
public async Task<(User, GameToken)?> UserAndGameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
|
|
{
|
|
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
|
|
if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null;
|
|
|
|
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == mmAuth);
|
|
if (token == null) return null;
|
|
if (!allowUnapproved && !token.Approved) return null;
|
|
|
|
User? user = await this.UserFromGameToken(token);
|
|
|
|
if (user == null) return null;
|
|
|
|
return (user, token);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Web Token Shenanigans
|
|
|
|
public User? UserFromLighthouseToken(string lighthouseToken)
|
|
{
|
|
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
|
|
if (token == null) return null;
|
|
|
|
return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId);
|
|
}
|
|
|
|
public User? UserFromWebRequest(HttpRequest request)
|
|
{
|
|
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
|
|
|
|
return this.UserFromLighthouseToken(lighthouseToken);
|
|
}
|
|
|
|
public WebToken? WebTokenFromRequest(HttpRequest request)
|
|
{
|
|
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
|
|
|
|
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public async Task<Photo?> PhotoFromSubject(PhotoSubject subject)
|
|
=> await this.Photos.FirstOrDefaultAsync(p => p.PhotoSubjectIds.Contains(subject.PhotoSubjectId.ToString()));
|
|
public async Task RemoveUser(User? user)
|
|
{
|
|
if (user == null) return;
|
|
|
|
if (user.Location != null) this.Locations.Remove(user.Location);
|
|
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
|
|
if (lastContact != null) this.LastContacts.Remove(lastContact);
|
|
|
|
foreach (Slot slot in this.Slots.Where(s => s.CreatorId == user.UserId)) await this.RemoveSlot(slot, false);
|
|
|
|
this.AuthenticationAttempts.RemoveRange(this.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId));
|
|
this.HeartedProfiles.RemoveRange(this.HeartedProfiles.Where(h => h.UserId == user.UserId));
|
|
this.PhotoSubjects.RemoveRange(this.PhotoSubjects.Where(s => s.UserId == user.UserId));
|
|
this.HeartedLevels.RemoveRange(this.HeartedLevels.Where(h => h.UserId == user.UserId));
|
|
this.VisitedLevels.RemoveRange(this.VisitedLevels.Where(v => v.UserId == user.UserId));
|
|
this.RatedReviews.RemoveRange(this.RatedReviews.Where(r => r.UserId == user.UserId));
|
|
this.QueuedLevels.RemoveRange(this.QueuedLevels.Where(q => q.UserId == user.UserId));
|
|
this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.UserId == user.UserId));
|
|
this.GameTokens.RemoveRange(this.GameTokens.Where(t => t.UserId == user.UserId));
|
|
this.WebTokens.RemoveRange(this.WebTokens.Where(t => t.UserId == user.UserId));
|
|
this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId));
|
|
this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId));
|
|
this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId));
|
|
this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId));
|
|
|
|
this.Users.Remove(user);
|
|
|
|
await this.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task RemoveSlot(Slot slot, bool saveChanges = true)
|
|
{
|
|
if (slot.Location != null) this.Locations.Remove(slot.Location);
|
|
this.Slots.Remove(slot);
|
|
|
|
if (saveChanges) await this.SaveChangesAsync();
|
|
}
|
|
#nullable disable
|
|
} |