Refactoring, database structure changed

This commit is contained in:
2025-03-03 00:56:32 +01:00
Unverified
parent d3805ef3db
commit c603c41c0b
913 changed files with 21764 additions and 32775 deletions

View File

@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Mvc;
using WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account;
public class AccountFilterQuery : IFilterQuery<Database.Model.Accounts.Account>
{
#region PROPERTIES
[FromQuery(Name = "username")]
public string? Username { get; set; }
[FromQuery(Name = "email")]
public string? Email { get; set; }
[FromQuery(Name = "is_admin")]
public bool? IsAdmin { get; set; }
[FromQuery(Name = "active_date_from")]
public DateOnly? ActiveDateFrom { get; set; }
[FromQuery(Name = "active_date_to")]
public DateOnly? ActiveDateTo { get; set; }
[FromQuery(Name = "join_date_from")]
public DateOnly? JoinDateFrom { get; set; }
[FromQuery(Name = "join_date_to")]
public DateOnly? JoinDateTo { get; set; }
[FromQuery(Name = "description")]
public string? Description { get; set; }
[FromQuery(Name = "gender_id")]
public short? GenderId { get; set; }
#endregion
#region PUBLIC METHODS
public IEnumerable<Filter<Database.Model.Accounts.Account>> GetFilters() =>
[
new AccountUsernameFilter(Username),
new AccountEmailFilter(Email),
new AccountIsAdminFilter(IsAdmin),
new AccountActiveDateFromFilter(ActiveDateFrom),
new AccountActiveDateToFilter(ActiveDateTo),
new AccountJoinDateFromFilter(JoinDateFrom),
new AccountJoinDateToFilter(JoinDateTo),
new AccountDescriptionFilter(Description),
new AccountGenderIdFilter(GenderId),
];
#endregion
}

View File

@@ -0,0 +1,18 @@
using System.Linq.Expressions;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account;
public static class AccountOrderKeys
{
public static readonly Dictionary<string, Expression<Func<Database.Model.Accounts.Account, object?>>> Base = new Dictionary<string, Expression<Func<Database.Model.Accounts.Account, object?>>>
{
{ "id", x => x.Id },
{ "username", x => x.Username },
{ "email", x => x.Email },
{ "is_admin", x => x.IsAdmin },
{ "active_date", x => x.ActiveDate },
{ "join_date", x => x.JoinDate },
{ "description", x => x.Description },
{ "gender", x => x.Gender != null ? x.Gender.Name : null },
};
}

View File

@@ -0,0 +1,9 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.Account;
public class AccountRequest : IPasswordEditRequest
{
public string Username { get; set; } = null!;
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
public string PasswordConfirmation { get; set; } = null!;
}

View File

@@ -0,0 +1,17 @@
using FluentValidation;
using WatchIt.Database;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account;
public class AccountRequestValidator : AbstractValidator<AccountRequest>
{
public AccountRequestValidator(DatabaseContext database)
{
Include(new PasswordEditRequestValidator());
RuleFor(x => x.Username).MinimumLength(5)
.MaximumLength(50)
.CannotBeIn(database.Accounts, x => x.Username).WithMessage("Username was already used");
RuleFor(x => x.Email).EmailAddress()
.CannotBeIn(database.Accounts, x => x.Email).WithMessage("Email was already used");
}
}

View File

@@ -0,0 +1,17 @@
using WatchIt.DTO.Models.Controllers.Genders.Gender;
using WatchIt.DTO.Models.Generics.Image;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account;
public class AccountResponse
{
public long Id { get; set; }
public string Username { get; set; } = null!;
public string Email { get; set; } = null!;
public bool IsAdmin { get; set; }
public DateTimeOffset ActiveDate { get; set; }
public DateTimeOffset JoinDate { get; set; }
public string? Description { get; set; }
public GenderResponse? Gender { get; set; }
public ImageResponse? ProfilePicture { get; set; }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountActiveDateFromFilter : Filter<Database.Model.Accounts.Account>
{
public AccountActiveDateFromFilter(DateOnly? query) : base(x =>
(
query == null
||
x.ActiveDate.UtcDateTime.CompareTo(query.Value) >= 0
)) { }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountActiveDateToFilter : Filter<Database.Model.Accounts.Account>
{
public AccountActiveDateToFilter(DateOnly? query) : base(x =>
(
query == null
||
x.ActiveDate.UtcDateTime.CompareTo(query.Value) <= 0
)) { }
}

View File

@@ -0,0 +1,18 @@
using System.Text.RegularExpressions;
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountDescriptionFilter : Filter<Database.Model.Accounts.Account>
{
public AccountDescriptionFilter(string? descriptionRegex) : base(x =>
(
string.IsNullOrWhiteSpace(descriptionRegex)
||
(
!string.IsNullOrWhiteSpace(x.Description)
&&
Regex.IsMatch(x.Description, descriptionRegex, RegexOptions.IgnoreCase)
)
)) { }
}

View File

@@ -0,0 +1,18 @@
using System.Text.RegularExpressions;
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountEmailFilter : Filter<Database.Model.Accounts.Account>
{
public AccountEmailFilter(string? emailRegex) : base(x =>
(
string.IsNullOrWhiteSpace(emailRegex)
||
(
!string.IsNullOrWhiteSpace(x.Username)
&&
Regex.IsMatch(x.Username, emailRegex, RegexOptions.IgnoreCase)
)
)) { }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountGenderIdFilter : Filter<Database.Model.Accounts.Account>
{
public AccountGenderIdFilter(short? genderId) : base(x =>
(
genderId == null
||
x.GenderId == genderId
)) { }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountIsAdminFilter : Filter<Database.Model.Accounts.Account>
{
public AccountIsAdminFilter(bool? query) : base(x =>
(
query == null
||
x.IsAdmin == query
)) { }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountJoinDateFromFilter : Filter<Database.Model.Accounts.Account>
{
public AccountJoinDateFromFilter(DateOnly? query) : base(x =>
(
query == null
||
x.JoinDate.UtcDateTime.CompareTo(query.Value) >= 0
)) { }
}

View File

@@ -0,0 +1,13 @@
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountJoinDateToFilter : Filter<Database.Model.Accounts.Account>
{
public AccountJoinDateToFilter(DateOnly? query) : base(x =>
(
query == null
||
x.JoinDate.UtcDateTime.CompareTo(query.Value) <= 0
)) { }
}

View File

@@ -0,0 +1,18 @@
using System.Text.RegularExpressions;
using WatchIt.DTO.Query;
namespace WatchIt.DTO.Models.Controllers.Accounts.Account.Filters;
public record AccountUsernameFilter : Filter<Database.Model.Accounts.Account>
{
public AccountUsernameFilter(string? usernameRegex) : base(x =>
(
string.IsNullOrWhiteSpace(usernameRegex)
||
(
!string.IsNullOrWhiteSpace(x.Username)
&&
Regex.IsMatch(x.Username, usernameRegex, RegexOptions.IgnoreCase)
)
)) { }
}

View File

@@ -0,0 +1,10 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountBackgroundPicture;
public class AccountBackgroundPictureRequest
{
#region PROPERTIES
public Guid Id { get; set; }
#endregion
}

View File

@@ -0,0 +1,15 @@
using FluentValidation;
using WatchIt.Database;
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountBackgroundPicture;
public class AccountBackgroundPictureRequestValidator : AbstractValidator<AccountBackgroundPictureRequest>
{
#region CONSTRUCTORS
public AccountBackgroundPictureRequestValidator(DatabaseContext database)
{
RuleFor(x => x.Id).MustBeIn(database.PhotoBackgrounds, x => x.Id).WithMessage("Background does not exists");
}
#endregion
}

View File

@@ -0,0 +1,11 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountEmail;
public class AccountEmailRequest
{
#region PROPERTIES
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
#endregion
}

View File

@@ -0,0 +1,17 @@
using FluentValidation;
using WatchIt.Database;
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountEmail;
public class AccountEmailRequestValidator : AbstractValidator<AccountEmailRequest>
{
#region CONSTRUCTORS
public AccountEmailRequestValidator(DatabaseContext database)
{
RuleFor(x => x.Email).EmailAddress()
.CannotBeIn(database.Accounts, x => x.Email).WithMessage("Email is already used");
}
#endregion
}

View File

@@ -0,0 +1,6 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountLogout;
public class AccountLogoutRequest
{
public string? RefreshToken { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountPassword;
public class AccountPasswordRequest : IPasswordEditRequest
{
#region PROPERTIES
public string OldPassword { get; set; } = null!;
public string Password { get; set; } = null!;
public string PasswordConfirmation { get; set; } = null!;
#endregion
}

View File

@@ -0,0 +1,15 @@
using FluentValidation;
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountPassword;
public class AccountPasswordRequestValidator : AbstractValidator<AccountPasswordRequest>
{
#region CONSTRUCTORS
public AccountPasswordRequestValidator()
{
Include(new PasswordEditRequestValidator());
}
#endregion
}

View File

@@ -0,0 +1,11 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountProfileInfo;
public class AccountProfileInfoRequest
{
#region PROPERTIES
public string? Description { get; set; }
public short? GenderId { get; set; }
#endregion
}

View File

@@ -0,0 +1,16 @@
using FluentValidation;
using WatchIt.Database;
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountProfileInfo;
public class AccountProfileInfoRequestValidator : AbstractValidator<AccountProfileInfoRequest>
{
public AccountProfileInfoRequestValidator(DatabaseContext database)
{
RuleFor(x => x.Description).MaximumLength(1000);
When(x => x.GenderId.HasValue, () =>
{
RuleFor(x => x.GenderId!.Value).MustBeIn(database.Genders.Select(x => x.Id));
});
}
}

View File

@@ -0,0 +1,11 @@
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountUsername;
public class AccountUsernameRequest
{
#region PROPERTIES
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
#endregion
}

View File

@@ -0,0 +1,18 @@
using FluentValidation;
using WatchIt.Database;
namespace WatchIt.DTO.Models.Controllers.Accounts.AccountUsername;
public class AccountUsernameRequestValidator : AbstractValidator<AccountUsernameRequest>
{
#region CONSTRUCTORS
public AccountUsernameRequestValidator(DatabaseContext database)
{
RuleFor(x => x.Username).MinimumLength(5)
.MaximumLength(50)
.CannotBeIn(database.Accounts, x => x.Username).WithMessage("Username is already used");
}
#endregion
}

View File

@@ -0,0 +1,163 @@
using WatchIt.Database.Model.Accounts;
using WatchIt.DTO.Models.Controllers.Accounts.Account;
using WatchIt.DTO.Models.Controllers.Accounts.AccountBackgroundPicture;
using WatchIt.DTO.Models.Controllers.Accounts.AccountEmail;
using WatchIt.DTO.Models.Controllers.Accounts.AccountPassword;
using WatchIt.DTO.Models.Controllers.Accounts.AccountProfileInfo;
using WatchIt.DTO.Models.Controllers.Accounts.AccountUsername;
using WatchIt.DTO.Models.Controllers.Genders;
using WatchIt.DTO.Models.Generics.Image;
namespace WatchIt.DTO.Models.Controllers.Accounts;
public static class AccountsMappers
{
#region PUBLIC METHODS
#region Account
public static Database.Model.Accounts.Account ToEntity(this AccountRequest request, Func<string, PasswordData> passwordGenFunc)
{
PasswordData generatedPassword = passwordGenFunc(request.Password);
return new Database.Model.Accounts.Account
{
Username = request.Username,
Email = request.Email,
Password = generatedPassword.PasswordHash,
LeftSalt = generatedPassword.LeftSalt,
RightSalt = generatedPassword.RightSalt,
};
}
public static AccountResponse ToResponse(this Database.Model.Accounts.Account account) => new AccountResponse
{
Id = account.Id,
Username = account.Username,
Email = account.Email,
IsAdmin = account.IsAdmin,
JoinDate = account.JoinDate,
ActiveDate = account.ActiveDate,
Description = account.Description,
Gender = account.Gender?.ToResponse(),
ProfilePicture = account.ProfilePicture?.ToResponse(),
};
public static void UpdateActiveDate(this Database.Model.Accounts.Account account)
{
account.ActiveDate = DateTimeOffset.UtcNow;
}
#endregion
#region AccountUsername
public static void UpdateWithRequest(this Database.Model.Accounts.Account account, AccountUsernameRequest request)
{
account.Username = request.Username;
}
#endregion
#region AccountEmail
public static void UpdateWithRequest(this Database.Model.Accounts.Account account, AccountEmailRequest request)
{
account.Email = request.Email;
}
#endregion
#region AccountPassword
public static void UpdateWithRequest(this Database.Model.Accounts.Account account, AccountPasswordRequest request, Func<string, PasswordData> passwordGenFunc)
{
PasswordData generatedPassword = passwordGenFunc(request.Password);
account.Password = generatedPassword.PasswordHash;
account.LeftSalt = generatedPassword.LeftSalt;
account.RightSalt = generatedPassword.RightSalt;
}
#endregion
#region AccountProfileInfo
public static void UpdateWithRequest(this Database.Model.Accounts.Account account, AccountProfileInfoRequest request)
{
account.Description = request.Description;
account.GenderId = request.GenderId;
}
public static AccountProfileInfoRequest ToProfileInfoRequest(this AccountResponse response) => new AccountProfileInfoRequest
{
Description = response.Description,
GenderId = response.Gender?.Id
};
#endregion
#region AccountProfilePicture
public static AccountProfilePicture ToEntity(this ImageRequest request, long accountId) => new AccountProfilePicture
{
AccountId = accountId,
Image = request.Image,
MimeType = request.MimeType,
};
#endregion
#region AccountBackgroundPicture
public static Database.Model.Accounts.AccountBackgroundPicture ToEntity(this AccountBackgroundPictureRequest request, long accountId) => new Database.Model.Accounts.AccountBackgroundPicture
{
AccountId = accountId,
BackgroundId = request.Id,
};
public static void UpdateWithRequest(this Database.Model.Accounts.AccountBackgroundPicture entity, AccountBackgroundPictureRequest request)
{
entity.BackgroundId = request.Id;
}
#endregion
#region AccountRefreshToken
public static AccountRefreshToken CreateAccountRefreshTokenEntity(Guid token, long accountId, DateTimeOffset expirationDate, bool isExtendable) => new AccountRefreshToken
{
Token = token,
AccountId = accountId,
ExpirationDate = expirationDate,
IsExtendable = isExtendable,
};
public static void UpdateExpirationDate(this AccountRefreshToken entity, DateTimeOffset expirationDate)
{
entity.ExpirationDate = expirationDate;
}
#endregion
#region AccountFollow
public static AccountFollow CreateAccountFollowEntity(long accountFollowerId, long accountFollowedId) => new AccountFollow
{
FollowerId = accountFollowerId,
FollowedId = accountFollowedId,
};
#endregion
#region PasswordData
public static PasswordData GetPasswordData(this Database.Model.Accounts.Account account) => new PasswordData
{
PasswordHash = account.Password,
LeftSalt = account.LeftSalt,
RightSalt = account.RightSalt,
};
#endregion
#endregion
}

View File

@@ -0,0 +1,11 @@
namespace WatchIt.DTO.Models.Controllers.Accounts;
public interface IPasswordEditRequest
{
#region PROPERTIES
string Password { get; set; }
string PasswordConfirmation { get; set; }
#endregion
}

View File

@@ -0,0 +1,12 @@
namespace WatchIt.DTO.Models.Controllers.Accounts;
public struct PasswordData
{
#region PROPERTIES
public required byte[] PasswordHash { get; set; }
public required string LeftSalt { get; set; }
public required string RightSalt { get; set; }
#endregion
}

View File

@@ -0,0 +1,24 @@
using FluentValidation;
namespace WatchIt.DTO.Models.Controllers.Accounts;
public class PasswordEditRequestValidator : AbstractValidator<IPasswordEditRequest>
{
#region CONSTRUCTORS
public PasswordEditRequestValidator()
{
RuleFor(x => x.Password).NotNull()
.NotEmpty();
When(x => x.Password is not null, () =>
{
RuleFor(x => x.Password).MinimumLength(8)
.Must(x => x.Any(char.IsUpper)).WithMessage("Password must contain at least one uppercase letter.")
.Must(x => x.Any(char.IsLower)).WithMessage("Password must contain at least one lowercase letter.")
.Must(x => x.Any(char.IsDigit)).WithMessage("Password must contain at least one digit.");
});
RuleFor(x => x.PasswordConfirmation).Equal(x => x.Password);
}
#endregion
}