transactions

This commit is contained in:
2025-08-24 14:18:20 +02:00
parent 1b2d95344a
commit 5777e351bf
107 changed files with 4940 additions and 1266 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
{"Email":"@system","Key":"caeae1bc-3761-4b30-8627-d86af99b0a4f","Password":"M8I^7b,UF!)PIQ.A"}

View File

@@ -0,0 +1,8 @@
namespace back.DataModels;
public partial class EfmigrationsLock
{
public int Id { get; set; }
public string Timestamp { get; set; } = null!;
}

44
back/DataModels/Event.cs Normal file
View File

@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Transactional.Abstractions;
namespace back.DataModels;
[Table("Events")]
public partial class Event : ISoftDeletable, IEquatable<Event>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(50)]
public string Title { get; set; } = null!;
[MaxLength(500)]
public string? Description { get; set; }
public string? Date { get; set; }
public string? Location { get; set; }
public string CreatedAt { get; set; } = null!;
public string UpdatedAt { get; set; } = null!;
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
public int IsDeleted { get; set; }
public string? DeletedAt { get; set; }
public virtual ICollection<Gallery> Galleries { get; set; } = [];
public virtual ICollection<Photo> Photos { get; set; } = [];
public virtual ICollection<Tag> Tags { get; set; } = [];
public override int GetHashCode()
=> HashCode.Combine(Id, Title, Date, Location);
public override bool Equals(object? obj)
=> obj is Event otherEvent && Equals(otherEvent);
public bool Equals(Event? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id
|| (Title == other.Title && Date == other.Date && Location == other.Location)
|| GetHashCode() == other.GetHashCode();
}
}

View File

@@ -1,55 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Events")]
public class EventModel
{
[Key]
public string Id { get; set; }
[Required, MaxLength(50)]
public string? Title { get; set; }
[MaxLength(500)]
public string? Description { get; set; }
public DateTime? Date { get; set; }
public string? Location { get; set; }
[ForeignKey("TagId")]
public List<TagModel> RelatedTags { get; set; } = [];
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
public EventModel() { }
public EventModel(string id)
{
Id = id;
}
public EventModel(
string id,
string title,
string description,
DateTime date,
string location,
List<TagModel>? relatedTags = null,
DateTime? createdAt = null,
DateTime? updatedAt = null,
string? createdBy = null,
string? updatedBy = null)
{
Id = id;
Title = title;
Description = description;
Date = date;
Location = location;
RelatedTags = relatedTags ?? [];
CreatedAt = createdAt ?? DateTime.UtcNow;
UpdatedAt = updatedAt ?? DateTime.UtcNow;
CreatedBy = createdBy ?? "";
UpdatedBy = updatedBy;
}
}

View File

@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Transactional.Abstractions;
namespace back.DataModels;
[Table("Galleries")]
public partial class Gallery: IEquatable<Gallery>, ISoftDeletable
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[MaxLength(100)]
public string? Title { get; set; }
[MaxLength(500)]
public string? Description { get; set; }
public string? CreatedAt { get; set; }
public string? UpdatedAt { get; set; }
public string CreatedBy { get; set; } = null!;
public int? IsPublic { get; set; }
public int? IsArchived { get; set; }
public int? IsFavorite { get; set; }
public int IsDeleted { get; set; }
public string? DeletedAt { get; set; }
public string? EventId { get; set; }
public virtual User CreatedByNavigation { get; set; } = null!;
public virtual Event? Event { get; set; }
public virtual ICollection<Photo> Photos { get; set; } = [];
public virtual ICollection<Tag> Tags { get; set; } = [];
public virtual ICollection<User> Users { get; set; } = [];
public Gallery() { }
public override int GetHashCode() => HashCode.Combine(Id, Title);
public override bool Equals(object? obj) => obj is Gallery otherEvent && Equals(otherEvent);
public bool Equals(Gallery? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id || GetHashCode() == other.GetHashCode();
}
}

View File

@@ -1,30 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Galleries")]
public class GalleryModel
{
public GalleryModel() { }
[Key]
public string Id { get; set; }
[MaxLength(100)]
public string? Title { get; set; }
[MaxLength(500)]
public string? Description { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
[ForeignKey("PhotoId")]
public List<PhotoModel> Photos { get; set; } = [];
public bool? IsPublic { get; set; } = true;
public bool? IsArchived { get; set; } = false;
public bool? IsFavorite { get; set; } = false;
public EventModel? Event { get; set; } = null;
public List<TagModel>? Tags { get; set; } = null;
public List<PersonModel>? PersonsInvolved { get; set; } = null;
public List<PersonModel>? UsersWhoCanSee { get; set; } = null;
}

View File

@@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Permissions")]
public partial class Permission: IEquatable<Permission>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(100)]
public string Name { get; set; } = null!;
[MaxLength(255)]
public string? Description { get; set; }
public virtual ICollection<Role> Roles { get; set; } = [];
public override int GetHashCode() => HashCode.Combine(Id, Name);
public override bool Equals(object? obj)
=> obj is Permission other && Equals(other);
public bool Equals(Permission? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id || GetHashCode() == other.GetHashCode();
}
// Static permissions
public static readonly Permission ViewContentPermission = new() { Id = "1", Name = "VIEW_CONTENT", Description = "Permission to view content" };
public static readonly Permission LikeContentPermission = new() { Id = "2", Name = "LIKE_CONTENT", Description = "Permission to like content" };
public static readonly Permission EditContentPermission = new() { Id = "3", Name = "EDIT_CONTENT", Description = "Permission to edit content" };
public static readonly Permission DeleteContentPermission = new() { Id = "4", Name = "DELETE_CONTENT", Description = "Permission to delete content" };
public static readonly Permission CreateContentPermission = new() { Id = "5", Name = "CREATE_CONTENT", Description = "Permission to create new content" };
public static readonly Permission EditUserPermission = new() { Id = "6", Name = "EDIT_USER", Description = "Permission to edit user" };
public static readonly Permission DeleteUserPermission = new() { Id = "7", Name = "DELETE_USER", Description = "Permission to delete user" };
public static readonly Permission DisableUserPermission = new() { Id = "8", Name = "DISABLE_USER", Description = "Permission to disable user" };
public static readonly Permission CreateUserPermission = new() { Id = "9", Name = "CREATE_USER", Description = "Permission to create new user" };
public static readonly Permission EditWebConfigPermission = new() { Id = "10", Name = "EDIT_WEB_CONFIG", Description = "Permission to edit web configuration" };
}

View File

@@ -1,36 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Permissions")]
public class PermissionModel
{
public PermissionModel() { }
[Key]
public string Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public PermissionModel(string id, string name, string description)
{
Id = id;
Name = name;
Description = description;
}
// Static permissions
public static readonly PermissionModel ViewContentPermission = new("1", "VIEW_CONTENT", "Permission to view content");
public static readonly PermissionModel LikeContentPermission = new("2", "LIKE_CONTENT", "Permission to like content");
public static readonly PermissionModel EditContentPermission = new("3", "EDIT_CONTENT", "Permission to edit content");
public static readonly PermissionModel DeleteContentPermission = new("4", "DELETE_CONTENT", "Permission to delete content");
public static readonly PermissionModel CreateContentPermission = new("5", "CREATE_CONTENT", "Permission to create new content");
public static readonly PermissionModel EditUserPermission = new("6", "EDIT_USER", "Permission to edit user");
public static readonly PermissionModel DeleteUserPermission = new("7", "DELETE_USER", "Permission to delete user");
public static readonly PermissionModel DisableUserPermission = new("8", "DISABLE_USER", "Permission to disable user");
public static readonly PermissionModel CreateUserPermission = new("9", "CREATE_USER", "Permission to create new user");
public static readonly PermissionModel EditWebConfigPermission = new("10", "EDIT_WEB_CONFIG", "Permission to edit web configuration");
}

51
back/DataModels/Person.cs Normal file
View File

@@ -0,0 +1,51 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Transactional.Abstractions;
namespace back.DataModels;
[Table("Persons")]
public partial class Person: IEquatable<Person>, ISoftDeletable
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(100)]
public string Name { get; set; } = null!;
public string? ProfilePicture { get; set; }
public string? Avatar { get; set; }
public string? SocialMediaId { get; set; }
[MaxLength(250)]
public string? Bio { get; set; } // Optional field for a short biography or description
public string CreatedAt { get; set; } = null!;
public string? UpdatedAt { get; set; }
public int IsDeleted { get; set; }
public string? DeletedAt { get; set; }
public virtual ICollection<Photo> Photos { get; set; } = [];
public virtual SocialMedia? SocialMedia { get; set; }
public virtual User? User { get; set; }
public virtual ICollection<Photo> PhotosNavigation { get; set; } = [];
public override int GetHashCode() => HashCode.Combine(Id, Name);
public override bool Equals(object? obj)
=> obj is Person other && Equals(other);
public bool Equals(Person? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id || GetHashCode() == other.GetHashCode();
}
public const string SystemPersonId = "00000000-0000-0000-0000-000000000001";
public static readonly Person SystemPerson = new()
{
Id = SystemPersonId,
Name = "System",
CreatedAt = DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss zz"),
User = User.SystemUser
};
}

View File

@@ -1,54 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Persons")]
public class PersonModel
{
public PersonModel() { }
[Key]
public string Id { get; set; }
[Required, MaxLength(100)]
public string? Name { get; set; }
public string? ProfilePicture { get; set; }
public string? Avatar { get; set; }
[ForeignKey("SocialMediaId")]
public SocialMedia? SocialMedia { get; set; }
[MaxLength(250)]
public string? Bio { get; set; } // Optional field for a short biography or description
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; }
public PersonModel(string id)
{
Id = id;
}
public PersonModel(string id, string name, string? profilePicture = null, string? avatar = null, SocialMedia? socialMedia = null)
{
Id = id;
Name = name;
ProfilePicture = profilePicture;
Avatar = avatar;
SocialMedia = socialMedia;
}
}
[Table("SocialMedia")]
public class SocialMedia
{
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string? Facebook { get; set; }
public string? Instagram { get; set; }
public string? Twitter { get; set; }
public string? BlueSky { get; set; }
public string? Tiktok { get; set; }
public string? Linkedin { get; set; }
public string? Pinterest { get; set; }
public string? Discord { get; set; }
public string? Reddit { get; set; }
public string? Other { get; set; }
}

47
back/DataModels/Photo.cs Normal file
View File

@@ -0,0 +1,47 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Photos")]
public partial class Photo : IEquatable<Photo>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(100), MinLength(1)]
public string Title { get; set; } = null!;
[MaxLength(500)]
public string? Description { get; set; }
public string? Extension { get; set; }
public string? LowResUrl { get; set; }
public string? MidResUrl { get; set; }
public string? HighResUrl { get; set; }
public string? CreatedAt { get; set; }
public string? UpdatedAt { get; set; }
public string CreatedBy { get; set; } = null!;
public string? UpdatedBy { get; set; }
public string? EventId { get; set; }
public string? RankingId { get; set; }
public int? IsFavorite { get; set; }
public int? IsPublic { get; set; }
public int? IsArchived { get; set; }
public virtual Person CreatedByNavigation { get; set; } = null!;
public virtual Event? Event { get; set; }
public virtual ICollection<Gallery> Galleries { get; set; } = [];
public virtual ICollection<Person> People { get; set; } = [];
public virtual ICollection<Tag> Tags { get; set; } = [];
public virtual ICollection<User> Users { get; set; } = [];
public override int GetHashCode() => HashCode.Combine(Id, Title);
public override bool Equals(object? obj)
=> obj is Photo otherEvent && Equals(otherEvent);
public bool Equals(Photo? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id || GetHashCode() == other.GetHashCode();
}
}

View File

@@ -1,74 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Photos")]
public class PhotoModel
{
public PhotoModel() { }
[Key]
public string Id { get; set; }
[Required, MaxLength(100), MinLength(1)]
public string Title { get; set; }
[MaxLength(500)]
public string Description { get; set; }
public string Extension { get; set; }
public string LowResUrl { get; set; }
public string MidResUrl { get; set; }
public string HighResUrl { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string CreatedBy { get; set; }
public string UpdatedBy { get; set; }
[ForeignKey("EventId")]
public EventModel? Event { get; set; } = null;
[ForeignKey("TagId")]
public List<TagModel> Tags { get; set; } = [];
[ForeignKey("RankingId")]
public RankingModel Ranking { get; set; } = new RankingModel(0);
public bool IsFavorite { get; set; } = false;
public bool IsPublic { get; set; } = true;
public bool IsArchived { get; set; } = false;
[ForeignKey("PersonId")]
public List<PersonModel>? Persons { get; set; }
public PhotoModel(
string id,
string title,
string description,
string lowResUrl,
string midResUrl,
string highResUrl,
DateTime createdAt,
DateTime updatedAt,
string createdBy,
string updatedBy,
EventModel? @event = null,
List<TagModel>? tags = null,
RankingModel? ranking = null,
bool isFavorite = false,
bool isPublic = true,
bool isArchived = false,
List<PersonModel>? persons = null)
{
Id = id;
Title = title;
Description = description;
LowResUrl = lowResUrl;
MidResUrl = midResUrl;
HighResUrl = highResUrl;
CreatedAt = createdAt;
UpdatedAt = updatedAt;
CreatedBy = createdBy;
UpdatedBy = updatedBy;
Event = @event ?? Event;
Tags = tags ?? Tags;
Ranking = ranking ?? Ranking;
IsFavorite = isFavorite;
IsPublic = isPublic;
IsArchived = isArchived;
Persons = persons ?? Persons;
}
}

119
back/DataModels/Ranking.cs Normal file
View File

@@ -0,0 +1,119 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
public class RankingGroup
{
public const float ExtremlyBad = 1/5f;
public const float Bad = 2 / 5f;
public const float Normal = 3 / 5f;
public const float Good = 4 / 5f;
public const float ExtremlyGood = 5 / 5f;
public static string GetGroup(float score) => (float)Math.Ceiling(score) switch
{
<= ExtremlyBad => nameof(ExtremlyBad),
<= Bad => nameof(Bad),
<= Normal => nameof(Normal),
<= Good => nameof(Good),
_ => nameof(ExtremlyGood)
};
}
[Table("Rankings")]
public partial class Ranking : IEquatable<Ranking>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public int TotalVotes { get; set; }
public int UpVotes { get; set; }
public int DownVotes { get; set; }
public Ranking(int totalVotes, int upVotes = 0, int downVotes = 0)
{
TotalVotes = totalVotes;
UpVotes = upVotes;
DownVotes = downVotes;
}
public Ranking()
{
TotalVotes = 0;
UpVotes = 0;
DownVotes = 0;
}
public void DownVote()
{
DownVotes++;
TotalVotes++;
}
public void UpVote()
{
UpVotes++;
TotalVotes++;
}
public float Score
{
get
{
if (TotalVotes == 0) return 0;
return (float)(UpVotes - DownVotes) / TotalVotes;
}
}
public string Group => RankingGroup.GetGroup(Score);
public override int GetHashCode() => HashCode.Combine(Id, TotalVotes, UpVotes, DownVotes);
public override bool Equals(object? obj) => obj is Ranking otherEvent && Equals(otherEvent);
public bool Equals(Ranking? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return
Id == other.Id
|| GetHashCode() == other.GetHashCode()
|| (TotalVotes == other.TotalVotes && UpVotes == other.UpVotes && DownVotes == other.DownVotes);
}
public static bool operator ==(Ranking ranking1, Ranking ranking2)
{
if (ranking1 is null && ranking2 is null) return true;
if (ranking1 is null || ranking2 is null) return false;
return ranking1.Equals(ranking2);
}
public static bool operator !=(Ranking ranking1, Ranking ranking2)
{
if (ranking1 is null && ranking2 is null) return false;
if (ranking1 is null || ranking2 is null) return true;
return !ranking1.Equals(ranking2);
}
public static bool operator < (Ranking ranking1, Ranking ranking2)
{
ArgumentNullException.ThrowIfNull(ranking1, nameof(ranking1));
ArgumentNullException.ThrowIfNull(ranking2, nameof(ranking2));
return ranking1.Score < ranking2.Score;
}
public static bool operator > (Ranking ranking1, Ranking ranking2)
{
ArgumentNullException.ThrowIfNull(ranking1, nameof(ranking1));
ArgumentNullException.ThrowIfNull(ranking2, nameof(ranking2));
if (ranking1 is null && ranking2 is null) return true;
if (ranking1 is null || ranking2 is null) return false;
return ranking1.Score > ranking2.Score;
}
public static bool operator <= (Ranking ranking1, Ranking ranking2)
=> ranking1 == ranking2 || ranking1 < ranking2;
public static bool operator >= (Ranking ranking1, Ranking ranking2)
=> ranking1 == ranking2 || ranking1 > ranking2;
}

View File

@@ -1,50 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Rankings")]
public class RankingModel
{
[Key]
public string Id { get; set; } = Guid.NewGuid().ToString();
private int totalVotes;
private int upVotes;
private int downVotes;
public RankingModel(int totalVotes, int upVotes = 0, int downVotes = 0)
{
this.totalVotes = totalVotes;
this.upVotes = upVotes;
this.downVotes = downVotes;
}
public RankingModel()
{
totalVotes = 0;
upVotes = 0;
downVotes = 0;
}
public void DownVote()
{
downVotes++;
totalVotes++;
}
public void UpVote()
{
upVotes++;
totalVotes++;
}
public double Score
{
get
{
if (totalVotes == 0) return 0;
return (double)(upVotes - downVotes) / totalVotes;
}
}
}

94
back/DataModels/Role.cs Normal file
View File

@@ -0,0 +1,94 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Roles")]
public partial class Role : IEquatable<Role>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(100)]
public string Name { get; set; } = null!;
[MaxLength(250)]
public string? Description { get; set; }
public string? BaseRoleModelId { get; set; }
public virtual Role? BaseRoleModel { get; set; }
public virtual ICollection<Role> InverseBaseRoleModel { get; set; } = [];
public virtual ICollection<Permission> Permissions { get; set; } = new HashSet<Permission>();
public virtual ICollection<User> Users { get; set; } = [];
public bool IsAdmin() => BaseRoleModel != null ? BaseRoleModel.IsAdmin() : Id == AdminRole.Id;
public bool IsContentManager() => BaseRoleModel != null ? BaseRoleModel.IsContentManager() : Id == ContentManagerRole.Id;
public bool IsUser() => BaseRoleModel != null ? BaseRoleModel.IsUser() : Id == UserRole.Id;
public bool HasPermission(Permission permission)
{
var baseRoleHasPermission = BaseRoleModel != null && BaseRoleModel.HasPermission(permission);
return baseRoleHasPermission || Permissions.Any(p => p.Id == permission.Id);
}
public Role() { }
public Role(string id, string name, string description, List<Permission>? permissions = null, Role? baseRoleModel = null)
{
Id = id;
Name = name;
Description = description;
Permissions = permissions ?? [];
if (baseRoleModel != null)
{
BaseRoleModel = baseRoleModel;
BaseRoleModelId = baseRoleModel.Id;
foreach (var permission in baseRoleModel.Permissions)
{
if (!Permissions.Any(p => p.Id == permission.Id))
{
Permissions.Add(permission);
}
}
}
}
public override int GetHashCode() => HashCode.Combine(Id, Name);
public override bool Equals(object? obj) => obj is Role otherEvent && Equals(otherEvent);
public bool Equals(Role? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Id == other.Id || GetHashCode() == other.GetHashCode();
}
public static readonly Role UserRole = new(
"1", "User", "Role for regular users",
[
Permission.ViewContentPermission,
Permission.LikeContentPermission
]
);
public static readonly Role ContentManagerRole = new(
"2", "Content Manager", "Role for managing content",
[
Permission.CreateContentPermission,
Permission.EditContentPermission,
Permission.DeleteContentPermission
],
UserRole
);
public static readonly Role AdminRole = new(
"3", "Admin", "Administrator role with full permissions",
[
Permission.CreateUserPermission,
Permission.DisableUserPermission,
Permission.EditUserPermission,
Permission.DeleteUserPermission,
Permission.EditWebConfigPermission
],
ContentManagerRole
);
}

View File

@@ -1,74 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Tags("Roles")]
public class RoleModel
{
public RoleModel() { }
[Key]
public string Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[MaxLength(250)]
public string Description { get; set; }
[ForeignKey("PermissionId")]
public List<PermissionModel> Permissions { get; set; }
[ForeignKey("RoleId")]
public RoleModel? BaseRoleModel { get; set; }
public RoleModel(string id, string name, string description, List<PermissionModel>? permissions = null, RoleModel? baseRoleModel = null)
{
Id = id;
Name = name;
Description = description;
Permissions = permissions ?? new List<PermissionModel>();
BaseRoleModel = baseRoleModel;
if (baseRoleModel != null)
{
Permissions.AddRange(baseRoleModel.Permissions);
}
}
public bool IsAdmin => Id == AdminRole.Id;
public bool IsContentManager => Id == ContentManagerRole.Id;
public bool IsUser => Id == UserRole.Id;
public bool HasPermission(PermissionModel permission)
{
return Permissions.Exists(p => p.Id == permission.Id);
}
public static readonly RoleModel UserRole = new(
"1", "User", "Role for regular users",
new List<PermissionModel> {
PermissionModel.ViewContentPermission,
PermissionModel.LikeContentPermission
}
);
public static readonly RoleModel ContentManagerRole = new(
"2", "Content Manager", "Role for managing content",
new List<PermissionModel> {
PermissionModel.CreateUserPermission,
PermissionModel.DisableUserPermission,
PermissionModel.CreateContentPermission,
PermissionModel.EditContentPermission,
PermissionModel.DeleteContentPermission
},
UserRole
);
public static readonly RoleModel AdminRole = new(
"3", "Admin", "Administrator role with full permissions",
new List<PermissionModel> {
PermissionModel.CreateUserPermission,
PermissionModel.EditUserPermission,
PermissionModel.DeleteUserPermission,
PermissionModel.EditWebConfigPermission
},
ContentManagerRole
);
}

View File

@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("SocialMedia")]
public partial class SocialMedia: IEquatable<SocialMedia>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
public string? Facebook { get; set; }
public string? Instagram { get; set; }
public string? Twitter { get; set; }
public string? BlueSky { get; set; }
public string? Tiktok { get; set; }
public string? Linkedin { get; set; }
public string? Pinterest { get; set; }
public string? Discord { get; set; }
public string? Reddit { get; set; }
public string? Other { get; set; }
public virtual ICollection<Person> People { get; set; } = [];
public override int GetHashCode() => HashCode.Combine(Id);
public override bool Equals(object? obj) => obj is SocialMedia otherEvent && Equals(otherEvent);
public bool Equals(SocialMedia? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return GetHashCode() == other.GetHashCode();
}
}

View File

@@ -0,0 +1,15 @@
namespace back.DataModels;
public class SystemKey
{
public string Email { get; set; } = "@system";
public string Key { get; set; } = Guid.NewGuid().ToString();
public required string Password { get; set; }
public bool IsValid(string email, string password, string key)
{
return Email.Equals(email, StringComparison.InvariantCultureIgnoreCase) &&
Password.Equals(password, StringComparison.InvariantCulture) &&
Key.Equals(key, StringComparison.InvariantCulture);
}
}

32
back/DataModels/Tag.cs Normal file
View File

@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Tags")]
public partial class Tag: IEquatable<Tag>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, MaxLength(25)]
public string Name { get; set; } = null!;
[Required]
public string CreatedAt { get; set; } = null!;
public virtual ICollection<Event> Events { get; set; } = [];
public virtual ICollection<Gallery> Galleries { get; set; } = [];
public virtual ICollection<Photo> Photos { get; set; } = [];
public override int GetHashCode() => HashCode.Combine(Id, Name);
public override bool Equals(object? obj) => obj is Tag tag && Equals(tag);
public bool Equals(Tag? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Id == other.Id || GetHashCode() == other.GetHashCode();
}
}

View File

@@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Tags")]
public class TagModel
{
public TagModel() { }
public TagModel(string id, string? name = null)
{
Id = id;
Name = name;
}
[Key]
public string Id { get; init; }
[Required, MaxLength(25)]
public string? Name { get; init; }
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
}

57
back/DataModels/User.cs Normal file
View File

@@ -0,0 +1,57 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Users")]
public class User : IEquatable<User>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; } = null!;
[Required, EmailAddress]
public string Email { get; set; } = null!;
[Required, MinLength(8)]
public string Password { get; set; } = null!;
[Required]
public string Salt { get; set; } = null!;
public string CreatedAt { get; set; } = null!;
public virtual Person IdNavigation { get; set; } = null!;
public virtual ICollection<Gallery> Galleries { get; set; } = [];
public virtual ICollection<Gallery> GalleriesNavigation { get; set; } = [];
public virtual ICollection<Photo> Photos { get; set; } = [];
public virtual ICollection<Role> Roles { get; set; } = [];
public User() { }
public User(string id, string email, string password, DateTimeOffset createdAt)
{
Id = id;
Email = email;
Password = password;
CreatedAt = createdAt.ToString("dd-MM-yyyy HH:mm:ss zz");
}
public bool IsAdmin() => Roles.Any(r => r.IsAdmin());
public bool IsContentManager() => Roles.Any(r => r.IsContentManager());
public bool IsUser() => Roles.Any(r => r.IsUser());
public override int GetHashCode() => HashCode.Combine(Id, Email);
public override bool Equals(object? obj) => obj is User otherEvent && Equals(otherEvent);
public bool Equals(User? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Id == other.Id && Email == other.Email;
}
public const string SystemUserId = "00000000-0000-0000-0000-000000000001";
public static readonly User SystemUser = new(
id: SystemUserId,
email: "@system",
password: "",
createdAt: DateTime.UtcNow
);
}

View File

@@ -1,54 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Users")]
public class UserModel : PersonModel
{
[Required]
public string Email { get; set; }
[Required, MinLength(8)]
public string Password { get; set; }
[Required]
public string Salt { get; set; }
[ForeignKey("RoleId")]
public List<RoleModel> Role { get; set; }
public UserModel(string id, string email, string password, string name, List<RoleModel> role, DateTime createdAt, DateTime updatedAt)
: base(id, name)
{
Email = email;
Password = password;
Role = role;
CreatedAt = createdAt;
UpdatedAt = updatedAt;
}
public UserModel() { }
public bool IsAdmin => Role.Exists(r => r.IsAdmin);
public bool IsContentManager => Role.Exists(r => r.IsContentManager);
public bool IsUser => Role.Exists(r => r.IsUser);
public UserDto ToDto()
{
return new UserDto
{
Id = Id,
Name = Name,
Email = Email,
ProfilePicture = ProfilePicture,
Avatar = Avatar,
SocialMedia = SocialMedia,
Bio = Bio,
CreatedAt = CreatedAt,
UpdatedAt = UpdatedAt
};
}
}
public class UserDto : PersonModel
{
public required string Email { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace back.Options;
public sealed class MailServerOptions
{
public required string SmtpServer { get; set; }
public required int Puerto { get; set; }
public required string Usuario { get; set; }
public required string Password { get; set; }
public bool EnableSsl { get; set; }
}

View File

@@ -9,7 +9,6 @@ public class Program
var builder = WebApplication.CreateBuilder(args);
builder.Services.UseExtensions();
builder.Services.AddMemoryCache();
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi

View File

@@ -1,5 +1,5 @@
using back.context;
using back.Options;
using back.persistance.data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@@ -7,16 +7,9 @@ namespace back.ServicesExtensions;
public static partial class ServicesExtensions
{
private static IServiceCollection AddDatabaseContexts(this IServiceCollection services)
private static IServiceCollection AddDatabaseContext(this IServiceCollection services)
{
services
.AddContext<EventContext>()
.AddContext<GalleryContext>()
.AddContext<PersonContext>()
.AddContext<PhotoContext>()
.AddContext<TagContext>()
.AddContext<UserContext>()
;
services.AddContext<DataContext>();
return services;
}
@@ -32,6 +25,25 @@ public static partial class ServicesExtensions
{
options.UseDatabaseConfig(config);
});
using var scope = services.BuildServiceProvider().CreateScope();
var context = scope.ServiceProvider
.GetRequiredService<T>();
var isDevelopment = scope.ServiceProvider
.GetRequiredService<IHostEnvironment>()
.IsDevelopment();
if (isDevelopment && !context.Database.HasPendingModelChanges())
{
context.Database.EnsureCreated();
}
else
{
context.Database.EnsureCreated();
context.Database.Migrate();
}
context.SaveChanges();
return services;
}
}

View File

@@ -1,5 +1,6 @@
using back.Options;
using Microsoft.EntityFrameworkCore;
using System.Text.RegularExpressions;
namespace back.ServicesExtensions;
@@ -59,6 +60,14 @@ public static partial class DbContextOptionsBuilderExtensions
switch (provider)
{
case DatabaseProvider.Sqlite:
var match = SQLiteRegex().Match(config.ConnectionString ?? string.Empty);
if (match.Success)
{
string? folder = null;
string path = match.Groups[1].Value.Replace("\\", "/");
folder = path.Contains('/') ? path[..path.IndexOf('/')] : path;
Directory.CreateDirectory(folder);
}
options.UseSqlite(config.ConnectionString);
break;
case DatabaseProvider.InMemory:
@@ -77,4 +86,7 @@ public static partial class DbContextOptionsBuilderExtensions
throw new InvalidOperationException($"Unsupported database provider: {config.Provider}");
}
}
[GeneratedRegex(@"Data Source=([^;]+)")]
private static partial Regex SQLiteRegex();
}

View File

@@ -12,10 +12,11 @@ public static partial class ServicesExtensions
services.Configure<Databases>(config.GetSection(nameof(Databases)));
services.Configure<DatabaseConfig>(DatabaseConfig.DataStorage, config.GetSection(DatabaseConfig.DataStorage));
services.Configure<DatabaseConfig>(DatabaseConfig.BlobStorage, config.GetSection(DatabaseConfig.BlobStorage));
services.Configure<MailServerOptions>(config.GetSection(nameof(MailServerOptions)));
services.PostConfigure<Databases>(databases =>
{
if (databases.BaseDirectory != null && !Directory.Exists(databases.BaseDirectory))
if (!string.IsNullOrEmpty(databases.BaseDirectory) && !Directory.Exists(databases.BaseDirectory))
{
try
{

View File

@@ -1,6 +1,10 @@
using back.persistance.blob;
using back.services.Crypto;
using back.services.ImageResizer;
using back.persistance.data;
using back.persistance.data.repositories;
using back.persistance.data.repositories.Abstracts;
using back.services.engine.SystemUser;
using DependencyInjector;
using Transactional.Abstractions.Interfaces;
using Transactional.Implementations.EntityFramework;
namespace back.ServicesExtensions;
@@ -8,15 +12,20 @@ public static partial class ServicesExtensions
{
public static IServiceCollection UseExtensions(this IServiceCollection services)
{
var config = services.ConfigureOptions();
//var config =
services.ConfigureOptions();
services.AddDatabaseContexts();
services.AddMemoryCache();
// TODO: Move and configure for using S3, Azure Blob Storage, etc.
services.AddSingleton<IBlobStorageService, FileSystemImageStorageService>();
services.AddDatabaseContext();
services.AddServices();
services.AddScoped<ITransactionalService<DataContext>, EntityFrameworkTransactionalService<DataContext>>();
using var scope = services.BuildServiceProvider().CreateScope();
scope.ServiceProvider
.GetRequiredService<ISystemUserGenerator>().GenerateAsync().Wait();
services.AddSingleton<IImageResizer, ImageResizer>();
services.AddSingleton<ICryptoService, CryptoService>();
return services;
}

View File

@@ -1,14 +1,21 @@
{
"Databases": {
"BaseDirectory": "data",
"BaseDirectory": ".program_data",
"Data": {
"Provider": "sqlite",
"ConnectionString": "Data Source=data/app.db;Cache=Shared"
"ConnectionString": "Data Source=.program_data/app.db"
},
"Blob": {
"Provider": "system",
"baseUrl": "https://localhost:7273/api/photo/{id}/{res}",
"SystemContainer": "imgs"
}
},
"MailServerOptions": {
"SmtpServer": "smtp.gmail.com",
"Puerto": 587,
"Usuario": "",
"Password": "",
"EnableSsl": true
}
}

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.14.2" />
<PackageReference Include="MailKit" Version="4.13.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
@@ -42,7 +43,8 @@
</ItemGroup>
<ItemGroup>
<Folder Include="persistance\data\" />
<ProjectReference Include="..\..\nuget\DependencyInjector\DependencyInjector.csproj" />
<ProjectReference Include="..\..\nuget\Transactional\Transactional.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36401.2 d17.14
VisualStudioVersion = 17.14.36401.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "back", "back.csproj", "{392278F3-4B36-47F4-AD31-5FBFCC181AD4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transactional", "..\..\nuget\Transactional\Transactional.csproj", "{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjector", "..\..\nuget\DependencyInjector\DependencyInjector.csproj", "{DBDF84A4-235C-4F29-8626-5BD1DC255970}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +19,14 @@ Global
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Release|Any CPU.Build.0 = Release|Any CPU
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Release|Any CPU.Build.0 = Release|Any CPU
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,52 +0,0 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.context;
public class EventContext : DbContext
{
private DbSet<EventModel> Events { get; set; }
public EventContext(DbContextOptions<EventContext> options) : base(options)
{
Database.EnsureCreated();
}
public async Task<EventModel?> GetById(string id)
{
return await GetById(Guid.Parse(id));
}
public async Task<EventModel?> GetById(Guid id)
{
return await Events.FindAsync(id);
}
public async Task<int> GetTotalItems()
{
return await Events.CountAsync();
}
public async Task<IEnumerable<EventModel>> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
return await Events
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<bool> Exists(EventModel? photo)
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Events.AnyAsync(p => p.Id == photo.Id);
}
public async Task<bool> Exists(string id)
{
return await Events.AnyAsync(p => p.Id == id);
}
}

View File

@@ -1,48 +0,0 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.context;
public class GalleryContext : DbContext
{
public DbSet<GalleryModel> Galleries { get; set; }
public GalleryContext(DbContextOptions<GalleryContext> options) : base(options)
{
// Ensure database is created
Database.EnsureCreated();
}
public async Task<GalleryModel?> GetById(Guid id)
{
return await Galleries.FindAsync(id);
}
public async Task<int> GetTotalItems()
{
return await Galleries.CountAsync();
}
public async Task<IEnumerable<GalleryModel>> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
return await Galleries
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<bool> Exists(GalleryModel? photo)
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Galleries.AnyAsync(p => p.Id == photo.Id);
}
public async Task<bool> Exists(string id)
{
return await Galleries.AnyAsync(p => p.Id == id);
}
}

View File

@@ -1,53 +0,0 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.context;
public class PersonContext : DbContext
{
private DbSet<PersonModel> Persons { get; set; }
public PersonContext(DbContextOptions<PersonContext> options) : base(options)
{
// Ensure database is created
Database.EnsureCreated();
}
public async Task<PersonModel?> GetById(string id)
{
return await GetById(Guid.Parse(id));
}
public async Task<PersonModel?> GetById(Guid id)
{
return await Persons.FindAsync(id);
}
public async Task<int> GetTotalItems()
{
return await Persons.CountAsync();
}
public async Task<IEnumerable<PersonModel>> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
return await Persons
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<bool> Exists(PersonModel? photo)
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Persons.AnyAsync(p => p.Id == photo.Id);
}
public async Task<bool> Exists(string id)
{
return await Persons.AnyAsync(p => p.Id == id);
}
}

View File

@@ -1,270 +0,0 @@
using back.DataModels;
using back.DTO;
using back.persistance.blob;
using back.services.ImageResizer;
using Microsoft.EntityFrameworkCore;
namespace back.context;
public class PhotoContext : DbContext
{
private DbSet<PhotoModel> Photos { get; set; }
private readonly IImageResizer _Resizer;
private readonly IBlobStorageService _BlobStorage;
private readonly TagContext _tagContext;
private readonly EventContext _eventContext;
private readonly PersonContext _personContext;
public PhotoContext(DbContextOptions<PhotoContext> options,
IImageResizer resizer,
IBlobStorageService blobStorage,
TagContext tags,
EventContext events,
PersonContext persons
) : base(options)
{
// Ensure database is created
Database.EnsureCreated();
_Resizer = resizer;
_BlobStorage = blobStorage;
_tagContext = tags;
_eventContext = events;
_personContext = persons;
}
public async Task CreateNew(PhotoFormModel? form)
{
if (form == null) { return; }
var photo = new PhotoModel(
Guid.NewGuid().ToString(),
form.Title,
form.Description ?? string.Empty,
string.Empty, // LowResUrl will be set later
string.Empty, // MidResUrl will be set later
string.Empty, // HighResUrl will be set later
DateTime.UtcNow,
DateTime.UtcNow,
form.UserId,
form.UserId
)
{
IsPublic = form.IsPublic
};
List<Task> tasks = [
SaveBlob(photo, form),
LinkTags(photo, form.Tags ?? [], form.UserId),
LinkEvent(photo, form.Evento ?? "", form.UserId),
LinkPersons(photo, form.People ?? [], form.UserId),
];
await Task.WhenAll(tasks);
await Photos.AddAsync(photo);
await SaveChangesAsync();
}
private async Task LinkPersons(PhotoModel photo, string[] personas, string updatedBy = "SYSTEM")
{
if (photo == null || personas == null || personas.Length == 0) return;
foreach (var personId in personas)
{
var person = await _personContext.GetById(personId);
if (person != null)
{
await LinkPersons(photo, person, updatedBy);
}
}
}
private async Task LinkPersons(PhotoModel photo, PersonModel tag, string updatedBy = "SYSTEM")
{
if (tag == null) return;
// Ensure the tag exists
if (await _personContext.Exists(tag.Id))
{
photo.Persons ??= [];
photo.Persons.Add(tag);
photo.UpdatedAt = DateTime.UtcNow;
photo.UpdatedBy = updatedBy; // or use a more appropriate value
}
}
private async Task LinkTags(PhotoModel photo, string[] tags, string updatedBy = "SYSTEM")
{
if (photo == null || tags == null || tags.Length == 0) return;
foreach (var tagId in tags)
{
var tag = await _tagContext.GetById(tagId);
if (tag != null)
{
await LinkTag(photo, tag, updatedBy);
}
}
}
private async Task LinkTag(PhotoModel photo, TagModel tag, string updatedBy = "SYSTEM")
{
if (tag == null) return;
// Ensure the tag exists
if (await _tagContext.Exists(tag.Id))
{
photo.Tags.Add(tag);
photo.UpdatedAt = DateTime.UtcNow;
photo.UpdatedBy = updatedBy; // or use a more appropriate value
}
}
private async Task LinkEvent(PhotoModel photo, string eventId, string updatedBy = "SYSTEM")
{
if (string.IsNullOrEmpty(eventId)) return;
var evento = await _eventContext.GetById(eventId);
if (evento != null)
{
await LinkEvent(photo, evento, updatedBy);
}
}
private async Task LinkEvent(PhotoModel photo, EventModel? evento, string updatedBy = "SYSTEM")
{
if (evento == null) return;
// Ensure the event exists
if (await _eventContext.Exists(evento.Id))
{
photo.Event = evento;
photo.UpdatedAt = DateTime.UtcNow;
photo.UpdatedBy = updatedBy;
}
}
private async Task SaveBlob(PhotoModel photo, PhotoFormModel form)
{
if (form.Image != null && form.Image.Length > 0)
{
var lowRes = await _Resizer.ResizeImage(form.Image, 480);
var midRes = await _Resizer.ResizeImage(form.Image, 720);
// Upload images to blob storage
photo.Extension = form.Image.FileName.Split('.').Last();
photo.LowResUrl = $"low/{photo.Id}.webp";
photo.MidResUrl = $"mid/{photo.Id}.webp";
photo.HighResUrl = $"high/{photo.Id}.{photo.Extension}";
await _BlobStorage.SaveAsync(lowRes, photo.LowResUrl);
await _BlobStorage.SaveAsync(midRes, photo.MidResUrl);
await _BlobStorage.SaveAsync(form.Image.OpenReadStream(), photo.HighResUrl);
}
}
public async Task<PhotoModel?> GetById(string id)
{
return await GetById(Guid.Parse(id));
}
public async Task<PhotoModel?> GetById(Guid id)
{
try
{
return await Photos.FindAsync(id);
}
catch
{
return null;
}
}
public async Task<int> GetTotalItems()
{
try
{
return await Photos.CountAsync();
}
catch
{
return 0;
}
}
public async Task<IEnumerable<PhotoModel>?> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
try
{
return await Photos
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
catch
{
return null;
}
}
public async Task<bool> Exists(PhotoModel? photo)
{
try
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Photos.AnyAsync(p => p.Id == photo.Id);
}
catch
{
return false; // Handle exceptions gracefully
}
}
public async Task<bool> Exists(string id)
{
try
{
if (string.IsNullOrEmpty(id)) return false;
return await Photos.AnyAsync(p => p.Id == id);
}
catch
{
return false; // Handle exceptions gracefully
}
}
public async Task Delete(PhotoModel photo)
{
if (photo == null) return;
if (await Exists(photo))
{
// Delete the photo from blob storage
if (!string.IsNullOrEmpty(photo.LowResUrl))
await _BlobStorage.DeleteAsync(photo.LowResUrl);
if (!string.IsNullOrEmpty(photo.MidResUrl))
await _BlobStorage.DeleteAsync(photo.MidResUrl);
if (!string.IsNullOrEmpty(photo.HighResUrl))
await _BlobStorage.DeleteAsync(photo.HighResUrl);
Photos.Remove(photo);
await SaveChangesAsync();
}
}
public async Task Update(PhotoModel photo)
{
if (photo == null) return;
if (await Exists(photo))
{
var evento = photo.Event;
photo.Event = null;
await LinkEvent(photo, evento, photo.UpdatedBy);
var tags = photo.Tags.Select(t => t.Id);
photo.Tags.Clear();
await LinkTags(photo, [.. tags], photo.UpdatedBy);
var persons = photo.Persons?.Select(t => t.Id) ?? [];
photo.Persons = null;
await LinkPersons(photo, [.. persons], photo.UpdatedBy);
Photos.Update(photo);
await SaveChangesAsync();
}
}
}

View File

@@ -1,52 +0,0 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.context;
public class TagContext : DbContext
{
private DbSet<TagModel> Tags { get; set; }
public TagContext(DbContextOptions<TagContext> options) : base(options)
{
Database.EnsureCreated();
}
public async Task<TagModel?> GetById(string id)
{
return await GetById(Guid.Parse(id));
}
public async Task<TagModel?> GetById(Guid id)
{
return await Tags.FindAsync(id);
}
public async Task<int> GetTotalItems()
{
return await Tags.CountAsync();
}
public async Task<IEnumerable<TagModel>> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
return await Tags
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<bool> Exists(TagModel? photo)
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Tags.AnyAsync(p => p.Id == photo.Id);
}
public async Task<bool> Exists(string id)
{
return await Tags.AnyAsync(p => p.Id == id);
}
}

View File

@@ -1,142 +0,0 @@
using back.DataModels;
using back.services.Crypto;
using Microsoft.EntityFrameworkCore;
using System.Net;
namespace back.context;
public class UserContext : DbContext
{
public record HttpErrorMap(HttpStatusCode Code, string Description);
public static class Errors
{
public static readonly HttpErrorMap Unauthorized =
new(HttpStatusCode.Unauthorized, "Invalid user data. Email or password are wrong.");
public static readonly HttpErrorMap BadRequest =
new(HttpStatusCode.BadRequest, "Missing user data.");
}
public DbSet<UserModel> Users { get; set; }
private readonly ICryptoService _cryptoService;
public UserContext(
DbContextOptions<UserContext> options,
ICryptoService cryptoService
) : base(options)
{
_cryptoService = cryptoService ?? throw new ArgumentNullException(nameof(cryptoService));
// Ensure database is created
Database.EnsureCreated();
}
public async Task<UserModel?> Create(string clientId, UserModel user)
{
ArgumentNullException.ThrowIfNull(user);
if (await Exists(user))
{
return await GetById(Guid.Parse(user.Id)) ?? null;
}
if (string.IsNullOrEmpty(user.Id))
{
user.Id = Guid.NewGuid().ToString();
}
if (string.IsNullOrEmpty(user.Salt))
{
user.Salt = _cryptoService.Salt();
}
user.Password = _cryptoService.Decrypt(clientId, user.Password) ?? string.Empty;
user.Password = _cryptoService.Hash(user.Password + user.Salt + _cryptoService.Pepper()) ?? string.Empty;
user.CreatedAt = DateTime.UtcNow;
Users.Add(user);
await SaveChangesAsync();
return user;
}
public async Task<UserModel?> Update(UserModel user)
{
ArgumentNullException.ThrowIfNull(user);
if (!await Exists(user))
{
return null;
}
var existingUser = await GetById(Guid.Parse(user.Id));
if (existingUser == null) return null;
existingUser.Name = user.Name;
existingUser.Email = user.Email;
existingUser.UpdatedAt = DateTime.UtcNow;
Users.Update(existingUser);
await SaveChangesAsync();
return existingUser;
}
public async Task<bool> Delete(Guid id)
{
var user = await GetById(id);
if (user == null) return false;
Users.Remove(user);
await SaveChangesAsync();
return true;
}
public async Task<UserModel?> GetByEmail(string email)
{
if (string.IsNullOrEmpty(email)) return null;
return await Users.FirstOrDefaultAsync(u => u.Email == email);
}
public async Task<string> GetUserSaltByEmail(string email)
{
if (string.IsNullOrEmpty(email)) return string.Empty;
var user = await Users.FirstOrDefaultAsync(u => u.Email == email);
return user?.Salt ?? string.Empty;
}
public async Task<UserModel?> Login(string email, string password, string clientId)
{
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
var pass = _cryptoService.Decrypt(clientId, password) + await GetUserSaltByEmail(email) + _cryptoService.Pepper();
var hashedPassword = _cryptoService.Hash(pass);
var user = await Users
.FirstOrDefaultAsync(u => u.Email == email && u.Password == hashedPassword);
return user;
}
public async Task<UserModel?> GetById(Guid id)
{
return await Users.FindAsync(id);
}
public async Task<int> GetTotalItems()
{
return await Users.CountAsync();
}
public async Task<IEnumerable<UserModel>> GetPage(int page = 1, int pageSize = 20)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 20;
return await Users
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<bool> Exists(UserModel? photo)
{
if (photo == null) return false;
if (string.IsNullOrEmpty(photo.Id)) return false;
return await Users.AnyAsync(p => p.Id == photo.Id);
}
public async Task<bool> Exists(string id)
{
return await Users.AnyAsync(p => p.Id == id);
}
}

View File

@@ -0,0 +1,21 @@
using back.services.engine.Crypto;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace back.controllers;
[ApiController, Route("api/[controller]")]
public class CryptoController(ICryptoService cryptoService) : ControllerBase
{
[HttpGet("[action]")] public async Task<IActionResult> RSA([FromHeader(Name = "X-client-thumbprint")] string clientId)
{
if (string.IsNullOrWhiteSpace(clientId))
{
return BadRequest("Client ID is required.");
}
var key = cryptoService.GetPublicCertificate(clientId);
if (key == null)
return StatusCode((int)HttpStatusCode.InternalServerError, "Failed to generate RSA keys.");
return Ok(new { PublicKey = key });
}
}

View File

@@ -1,60 +1,33 @@
using back.context;
using back.DataModels;
using back.DataModels;
using back.DTO;
using back.persistance.blob;
using back.services.bussines.PhotoService;
using Microsoft.AspNetCore.Mvc;
namespace back.controllers;
[Route("api/[controller]")]
[ApiController]
public class PhotosController(PhotoContext photoContext, IBlobStorageService blobStorage) : ControllerBase
public class PhotosController(IPhotoService photoService) : ControllerBase
{
private readonly PhotoContext _photoContext = photoContext;
private readonly IPhotoService _photoService = photoService;
// GET: api/<PhotoController>
[HttpGet]
public async Task<ActionResult<IEnumerable<PhotoModel>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
public async Task<ActionResult<IEnumerable<Photo>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
{
var photos = await _photoContext.GetPage(page, pageSize);
var totalItems = await _photoContext.GetTotalItems();
(int totalItems, IEnumerable<Photo>? pageData) = await _photoService.GetPage(page, pageSize);
Response.Headers.Append("X-Total-Count", totalItems.ToString());
return Ok(photos);
return Ok(pageData);
}
// GET api/<PhotoController>/5
[HttpGet("{res}/{id}")]
public async Task<IActionResult> Get(string res, Guid id)
[HttpGet("{id}/{res}")]
public async Task<IActionResult> Get(string id, string res)
{
var photo = await _photoContext.GetById(id);
if (photo == null)
(string? mediaType, byte[]? fileBytes) = await _photoService.GetBytes(id, res.ToLower());
if(fileBytes == null)
return NotFound();
string? filePath = res.ToLower() switch
{
"high" => photo.HighResUrl,
"mid" => photo.MidResUrl,
"low" or _ => photo.LowResUrl
};
string? mediaType = res.ToLower() switch
{
"high" => $"image/{photo.Extension}",
"mid" or "low" or _ => "image/webp",
};
if (filePath == null)
{
return NotFound();
}
var file = await blobStorage.GetBytesAsync(filePath);
if (file == null)
{
return NotFound();
}
return File(file, mediaType);
return File(fileBytes, mediaType ?? "image/jpeg");
}
// POST api/<PhotoController>
@@ -66,7 +39,7 @@ public class PhotosController(PhotoContext photoContext, IBlobStorageService blo
if (form.Image == null || form.Image.Length == 0)
return BadRequest("No image uploaded.");
await _photoContext.CreateNew(form);
await _photoService.Create(form);
return Created();
}
@@ -78,21 +51,17 @@ public class PhotosController(PhotoContext photoContext, IBlobStorageService blo
//// PUT api/<PhotoController>
[HttpPut]
public async Task<IActionResult> Put([FromBody] PhotoModel photo)
public async Task<IActionResult> Put([FromBody] Photo photo)
{
await _photoContext.Update(photo);
await _photoService.Update(photo);
return NoContent();
}
// DELETE api/<PhotoController>/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid id)
public async Task<IActionResult> Delete(string id)
{
var photo = await _photoContext.GetById(id);
if (photo == null)
return NotFound();
await _photoContext.Delete(photo);
await _photoService.Delete(id);
return NoContent();
}
}

View File

@@ -1,14 +1,18 @@
using back.context;
using back.DataModels;
using back.DataModels;
using back.services.bussines;
using back.services.bussines.UserService;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace back.controllers;
public record UserLoginFromModel(string Email, string Password, string? SystemKey);
public record ForgotPasswordFromModel(string Email);
public record RegisterFromModel(string Name, string Email, string Password);
[ApiController, Route("api/[controller]")]
public class UsersController(UserContext userContext) : ControllerBase
public class UsersController(IUserService user) : ControllerBase
{
private readonly UserContext _userContext = userContext;
private readonly IUserService _user = user;
// GET: api/<UsersController>
//[HttpGet]
//public async Task<ActionResult<IEnumerable<UserModel>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
@@ -28,27 +32,59 @@ public class UsersController(UserContext userContext) : ControllerBase
// return Ok(user);
//}
[HttpPost]
[HttpPost("[action]")]
public async Task<IActionResult> Login(
[FromHeader(Name = "X-client-thumbprint")] string clientId,
[FromBody] UserModel user
[FromBody] UserLoginFromModel user
)
{
if (string.IsNullOrEmpty(clientId))
return BadRequest("Client ID cannot be null or empty");
if (user == null || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
return BadRequest(UserContext.Errors.BadRequest.Description);
var existingUser = await _userContext.Login(user.Email, user.Password, clientId);
return BadRequest(Errors.BadRequest.Description);
if (user.Email.Equals("@system", StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(user.SystemKey))
return Unauthorized(Errors.Unauthorized.Description);
var systemUser = await _user.ValidateSystemUser(user.Email, user.Password, user.SystemKey, clientId);
if (systemUser == null)
return Unauthorized(Errors.Unauthorized.Description);
return Ok(systemUser);
}
var existingUser = await _user.Login(user.Email, user.Password, clientId);
if (existingUser == null)
return Unauthorized(UserContext.Errors.Unauthorized.Description);
return Ok(existingUser.ToDto());
return Unauthorized(Errors.Unauthorized.Description);
return Ok(existingUser);
}
//// POST api/<UsersController>
//[HttpPost]
//public async Task<IActionResult> Post([FromBody] UserModel user)
//{
// if (user == null)
// return BadRequest("User cannot be null");
// var createdUser = await _userContext.Create(user);
// return CreatedAtAction(nameof(Get), new { id = createdUser.Id }, createdUser);
//}
[HttpPost("forgot-password")]
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordFromModel user)
{
if (string.IsNullOrEmpty(user.Email))
return BadRequest("Email cannot be null or empty");
await _user.SendResetPassword(user.Email);
return Ok("If the email exists, a reset password link has been sent.");
}
// POST api/<UsersController>
[HttpPost("[action]")]
public async Task<IActionResult> Register(
[FromHeader(Name = "X-client-thumbprint")] string clientId,
[FromBody] RegisterFromModel user)
{
if (user == null)
return BadRequest("User cannot be null");
try
{
var createdUser = await _user.Create(clientId, new User() { Email = user.Email, Password = user.Password });
return Created();
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}

View File

@@ -5,41 +5,41 @@ using Microsoft.Extensions.Options;
namespace back.persistance.blob;
public class FileSystemImageStorageService(
IOptions<Databases> systemOptions,
IOptions<Databases> systemOptions,
IOptionsMonitor<DatabaseConfig> options,
IMemoryCache memoryCache
) : IBlobStorageService
) : IBlobStorageService
{
private readonly string RootPath = systemOptions.Value.BaseDirectory ?? "";
private readonly string RootPath = systemOptions.Value.BaseDirectory ?? "data";
private readonly DatabaseConfig config = options.Get(DatabaseConfig.BlobStorage);
private readonly IMemoryCache cache = memoryCache;
private string GetFullPath(string fileName)
{
// Ensure the directory exists
var directory = Path.Join(RootPath, config.SystemContainer, fileName);
var path = Path.Join(RootPath, config.SystemContainer, fileName);
var directory = Path.GetDirectoryName(path);
if (directory != null && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return fileName;
return path;
}
public async Task DeleteAsync(string fileName)
public async Task Delete(string fileName)
{
var path = GetFullPath(fileName);
if (cache.TryGetValue(path, out Stream cachedStream))
{
cachedStream.Dispose(); // Dispose the cached stream if it exists
cache.Remove(path); // Remove from cache
}
if (!File.Exists(path))
{
return; // No file to delete
}
try
{
File.Delete(path);
var path = GetFullPath(fileName);
if (cache.TryGetValue(path, out Stream? cachedStream))
{
cachedStream?.Dispose(); // Dispose the cached stream if it exists
cache.Remove(path); // Remove from cache
}
if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex)
{
@@ -47,14 +47,14 @@ public class FileSystemImageStorageService(
}
}
public Task<Stream?> GetStreamAsync(string fileName)
public async Task<Stream?> GetStream(string fileName)
{
var path = GetFullPath(fileName);
if (File.Exists(path))
{
if (cache.TryGetValue(path, out Stream? cachedStream))
{
return Task.FromResult<Stream?>(cachedStream);
return cachedStream;
}
// open the file stream for multiple reads and cache it for performance
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
@@ -63,14 +63,14 @@ public class FileSystemImageStorageService(
.SetValue(fileStream)
.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // Cache for 30 minutes
return Task.FromResult<Stream?>(fileStream);
return fileStream;
}
return Task.FromResult<Stream?>(null);
return null;
}
public async Task<byte[]?> GetBytesAsync(string fileName)
public async Task<byte[]?> GetBytes(string fileName)
{
var stream = await GetStreamAsync(fileName);
var stream = await GetStream(fileName);
if (stream != null)
{
using var memoryStream = new MemoryStream();
@@ -80,25 +80,25 @@ public class FileSystemImageStorageService(
return null;
}
public async Task SaveAsync(Stream blobStream, string fileName)
public async Task Save(Stream blobStream, string fileName)
{
var path = GetFullPath(fileName);
if (cache.TryGetValue(path, out Stream? _) || File.Exists(path))
{
throw new InvalidOperationException($"File {fileName} already exists. Use Update for updating file info.");
}
using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
await blobStream.CopyToAsync(fileStream);
}
public async Task UpdateAsync(Stream blobStream, string fileName)
public async Task Update(Stream blobStream, string fileName)
{
var path = GetFullPath(fileName);
if (File.Exists(path))
{
await DeleteAsync(fileName);
await Delete(fileName);
}
await SaveAsync(blobStream, fileName);
await Save(blobStream, fileName);
}
}

View File

@@ -1,11 +1,13 @@
namespace back.persistance.blob;
using DependencyInjector.Lifetimes;
public interface IBlobStorageService
namespace back.persistance.blob;
public interface IBlobStorageService : ISingleton
{
Task SaveAsync(Stream blobStream, string fileName);
Task<Stream?> GetStreamAsync(string fileName);
Task<byte[]?> GetBytesAsync(string fileName);
Task DeleteAsync(string fileName);
Task UpdateAsync(Stream blobStream, string fileName);
Task Save(Stream blobStream, string fileName);
Task<Stream?> GetStream(string fileName);
Task<byte[]?> GetBytes(string fileName);
Task Delete(string fileName);
Task Update(Stream blobStream, string fileName);
}

View File

@@ -0,0 +1,65 @@
using back.DataModels;
using back.persistance.data.relations;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data;
public partial class DataContext : DbContext
{
public DataContext() { }
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public virtual DbSet<EfmigrationsLock> EfmigrationsLocks { get; set; }
public virtual DbSet<Event> Events { get; set; }
public virtual DbSet<Gallery> Galleries { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<Photo> Photos { get; set; }
public virtual DbSet<Ranking> Rankings { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<SocialMedia> SocialMedia { get; set; }
public virtual DbSet<Tag> Tags { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EfmigrationsLock>(entity =>
{
entity.ToTable("__EFMigrationsLock");
entity.Property(e => e.Id).ValueGeneratedNever();
});
typeof(IRelationEstablisher).Assembly.GetExportedTypes()
.Where(t => typeof(IRelationEstablisher).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
.ToList()
.ForEach(seederType =>
{
var relationEstablisher = (IRelationEstablisher?)Activator.CreateInstance(seederType);
relationEstablisher?.EstablishRelation(modelBuilder);
});
//typeof(ISeeder).Assembly.GetExportedTypes()
// .Where(t => typeof(ISeeder).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
// .ToList()
// .ForEach(seederType =>
// {
// var seeder = (ISeeder?)Activator.CreateInstance(seederType);
// seeder?.Seed(modelBuilder);
// });
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,752 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using back.persistance.data;
#nullable disable
namespace back.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250824120656_InitialSetup")]
partial class InitialSetup
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("EventTag", b =>
{
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("EventId", "TagId");
b.HasIndex("TagId");
b.ToTable("EventTags", (string)null);
});
modelBuilder.Entity("GalleryPhoto", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "PhotoId");
b.HasIndex("PhotoId");
b.ToTable("GalleryPhotos", (string)null);
});
modelBuilder.Entity("GalleryTag", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "TagId");
b.HasIndex("TagId");
b.ToTable("GalleryTags", (string)null);
});
modelBuilder.Entity("GalleryUserViewer", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "UserId");
b.HasIndex("UserId");
b.ToTable("GalleryUserViewers", (string)null);
});
modelBuilder.Entity("PhotoPerson", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("PersonId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "PersonId");
b.HasIndex("PersonId");
b.ToTable("PhotoPersons", (string)null);
});
modelBuilder.Entity("PhotoTag", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "TagId");
b.HasIndex("TagId");
b.ToTable("PhotoTags", (string)null);
});
modelBuilder.Entity("PhotoUserBuyer", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "UserId");
b.HasIndex("UserId");
b.ToTable("PhotoUserBuyers", (string)null);
});
modelBuilder.Entity("RolePermission", b =>
{
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.Property<string>("PermissionId")
.HasColumnType("TEXT");
b.HasKey("RoleId", "PermissionId");
b.HasIndex("PermissionId");
b.ToTable("RolePermissions", (string)null);
});
modelBuilder.Entity("UserRole", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("back.DataModels.EfmigrationsLock", b =>
{
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<string>("Timestamp")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("__EFMigrationsLock", (string)null);
});
modelBuilder.Entity("back.DataModels.Event", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasColumnType("TEXT");
b.Property<string>("Date")
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<string>("Location")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Events");
});
modelBuilder.Entity("back.DataModels.Gallery", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<int?>("IsArchived")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<int?>("IsFavorite")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsPublic")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<string>("Title")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("EventId");
b.ToTable("Galleries");
});
modelBuilder.Entity("back.DataModels.Permission", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Permissions");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Avatar")
.HasColumnType("TEXT");
b.Property<string>("Bio")
.HasMaxLength(250)
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("ProfilePicture")
.HasColumnType("TEXT");
b.Property<string>("SocialMediaId")
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SocialMediaId");
b.ToTable("Persons");
});
modelBuilder.Entity("back.DataModels.Photo", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<string>("Extension")
.HasColumnType("TEXT");
b.Property<string>("HighResUrl")
.HasColumnType("TEXT");
b.Property<int?>("IsArchived")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsFavorite")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsPublic")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<string>("LowResUrl")
.HasColumnType("TEXT");
b.Property<string>("MidResUrl")
.HasColumnType("TEXT");
b.Property<string>("RankingId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("EventId");
b.ToTable("Photos");
});
modelBuilder.Entity("back.DataModels.Ranking", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("DownVotes")
.HasColumnType("INTEGER");
b.Property<int>("TotalVotes")
.HasColumnType("INTEGER");
b.Property<int>("UpVotes")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Rankings");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("BaseRoleModelId")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(250)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("BaseRoleModelId");
b.ToTable("Roles");
});
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("BlueSky")
.HasColumnType("TEXT");
b.Property<string>("Discord")
.HasColumnType("TEXT");
b.Property<string>("Facebook")
.HasColumnType("TEXT");
b.Property<string>("Instagram")
.HasColumnType("TEXT");
b.Property<string>("Linkedin")
.HasColumnType("TEXT");
b.Property<string>("Other")
.HasColumnType("TEXT");
b.Property<string>("Pinterest")
.HasColumnType("TEXT");
b.Property<string>("Reddit")
.HasColumnType("TEXT");
b.Property<string>("Tiktok")
.HasColumnType("TEXT");
b.Property<string>("Twitter")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("SocialMedia");
});
modelBuilder.Entity("back.DataModels.Tag", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(25)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "Name" }, "IX_Tags_Name")
.IsUnique();
b.ToTable("Tags");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("EventTag", b =>
{
b.HasOne("back.DataModels.Event", null)
.WithMany()
.HasForeignKey("EventId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("GalleryPhoto", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
});
modelBuilder.Entity("GalleryTag", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("GalleryUserViewer", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("PhotoPerson", b =>
{
b.HasOne("back.DataModels.Person", null)
.WithMany()
.HasForeignKey("PersonId")
.IsRequired();
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
});
modelBuilder.Entity("PhotoTag", b =>
{
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("PhotoUserBuyer", b =>
{
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("RolePermission", b =>
{
b.HasOne("back.DataModels.Permission", null)
.WithMany()
.HasForeignKey("PermissionId")
.IsRequired();
b.HasOne("back.DataModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.IsRequired();
});
modelBuilder.Entity("UserRole", b =>
{
b.HasOne("back.DataModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("back.DataModels.Gallery", b =>
{
b.HasOne("back.DataModels.User", "CreatedByNavigation")
.WithMany("Galleries")
.HasForeignKey("CreatedBy")
.IsRequired();
b.HasOne("back.DataModels.Event", "Event")
.WithMany("Galleries")
.HasForeignKey("EventId");
b.Navigation("CreatedByNavigation");
b.Navigation("Event");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.HasOne("back.DataModels.SocialMedia", "SocialMedia")
.WithMany("People")
.HasForeignKey("SocialMediaId");
b.Navigation("SocialMedia");
});
modelBuilder.Entity("back.DataModels.Photo", b =>
{
b.HasOne("back.DataModels.Person", "CreatedByNavigation")
.WithMany("Photos")
.HasForeignKey("CreatedBy")
.IsRequired();
b.HasOne("back.DataModels.Event", "Event")
.WithMany("Photos")
.HasForeignKey("EventId");
b.Navigation("CreatedByNavigation");
b.Navigation("Event");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.HasOne("back.DataModels.Role", "BaseRoleModel")
.WithMany("InverseBaseRoleModel")
.HasForeignKey("BaseRoleModelId");
b.Navigation("BaseRoleModel");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.HasOne("back.DataModels.Person", "IdNavigation")
.WithOne("User")
.HasForeignKey("back.DataModels.User", "Id")
.IsRequired();
b.Navigation("IdNavigation");
});
modelBuilder.Entity("back.DataModels.Event", b =>
{
b.Navigation("Galleries");
b.Navigation("Photos");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.Navigation("Photos");
b.Navigation("User");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.Navigation("InverseBaseRoleModel");
});
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
{
b.Navigation("People");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.Navigation("Galleries");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,583 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace back.Migrations
{
/// <inheritdoc />
public partial class InitialSetup : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "__EFMigrationsLock",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false),
Timestamp = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK___EFMigrationsLock", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Events",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
Date = table.Column<string>(type: "TEXT", nullable: true),
Location = table.Column<string>(type: "TEXT", nullable: true),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
UpdatedAt = table.Column<string>(type: "TEXT", nullable: false),
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
DeletedAt = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Events", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Permissions",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "TEXT", maxLength: 255, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Permissions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Rankings",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
TotalVotes = table.Column<int>(type: "INTEGER", nullable: false),
UpVotes = table.Column<int>(type: "INTEGER", nullable: false),
DownVotes = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Rankings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Roles",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "TEXT", maxLength: 250, nullable: true),
BaseRoleModelId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Roles", x => x.Id);
table.ForeignKey(
name: "FK_Roles_Roles_BaseRoleModelId",
column: x => x.BaseRoleModelId,
principalTable: "Roles",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "SocialMedia",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Facebook = table.Column<string>(type: "TEXT", nullable: true),
Instagram = table.Column<string>(type: "TEXT", nullable: true),
Twitter = table.Column<string>(type: "TEXT", nullable: true),
BlueSky = table.Column<string>(type: "TEXT", nullable: true),
Tiktok = table.Column<string>(type: "TEXT", nullable: true),
Linkedin = table.Column<string>(type: "TEXT", nullable: true),
Pinterest = table.Column<string>(type: "TEXT", nullable: true),
Discord = table.Column<string>(type: "TEXT", nullable: true),
Reddit = table.Column<string>(type: "TEXT", nullable: true),
Other = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SocialMedia", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.Id);
});
migrationBuilder.CreateTable(
name: "RolePermissions",
columns: table => new
{
RoleId = table.Column<string>(type: "TEXT", nullable: false),
PermissionId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RolePermissions", x => new { x.RoleId, x.PermissionId });
table.ForeignKey(
name: "FK_RolePermissions_Permissions_PermissionId",
column: x => x.PermissionId,
principalTable: "Permissions",
principalColumn: "Id");
table.ForeignKey(
name: "FK_RolePermissions_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Persons",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
ProfilePicture = table.Column<string>(type: "TEXT", nullable: true),
Avatar = table.Column<string>(type: "TEXT", nullable: true),
SocialMediaId = table.Column<string>(type: "TEXT", nullable: true),
Bio = table.Column<string>(type: "TEXT", maxLength: 250, nullable: true),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
DeletedAt = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Persons", x => x.Id);
table.ForeignKey(
name: "FK_Persons_SocialMedia_SocialMediaId",
column: x => x.SocialMediaId,
principalTable: "SocialMedia",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "EventTags",
columns: table => new
{
EventId = table.Column<string>(type: "TEXT", nullable: false),
TagId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EventTags", x => new { x.EventId, x.TagId });
table.ForeignKey(
name: "FK_EventTags_Events_EventId",
column: x => x.EventId,
principalTable: "Events",
principalColumn: "Id");
table.ForeignKey(
name: "FK_EventTags_Tags_TagId",
column: x => x.TagId,
principalTable: "Tags",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Photos",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
Extension = table.Column<string>(type: "TEXT", nullable: true),
LowResUrl = table.Column<string>(type: "TEXT", nullable: true),
MidResUrl = table.Column<string>(type: "TEXT", nullable: true),
HighResUrl = table.Column<string>(type: "TEXT", nullable: true),
CreatedAt = table.Column<string>(type: "TEXT", nullable: true),
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
EventId = table.Column<string>(type: "TEXT", nullable: true),
RankingId = table.Column<string>(type: "TEXT", nullable: true),
IsFavorite = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
IsPublic = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 1),
IsArchived = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0)
},
constraints: table =>
{
table.PrimaryKey("PK_Photos", x => x.Id);
table.ForeignKey(
name: "FK_Photos_Events_EventId",
column: x => x.EventId,
principalTable: "Events",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Photos_Persons_CreatedBy",
column: x => x.CreatedBy,
principalTable: "Persons",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Email = table.Column<string>(type: "TEXT", nullable: false),
Password = table.Column<string>(type: "TEXT", nullable: false),
Salt = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
table.ForeignKey(
name: "FK_Users_Persons_Id",
column: x => x.Id,
principalTable: "Persons",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "PhotoPersons",
columns: table => new
{
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
PersonId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PhotoPersons", x => new { x.PhotoId, x.PersonId });
table.ForeignKey(
name: "FK_PhotoPersons_Persons_PersonId",
column: x => x.PersonId,
principalTable: "Persons",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PhotoPersons_Photos_PhotoId",
column: x => x.PhotoId,
principalTable: "Photos",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "PhotoTags",
columns: table => new
{
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
TagId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PhotoTags", x => new { x.PhotoId, x.TagId });
table.ForeignKey(
name: "FK_PhotoTags_Photos_PhotoId",
column: x => x.PhotoId,
principalTable: "Photos",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PhotoTags_Tags_TagId",
column: x => x.TagId,
principalTable: "Tags",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Galleries",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
CreatedAt = table.Column<string>(type: "TEXT", nullable: true),
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
IsPublic = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 1),
IsArchived = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
IsFavorite = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
DeletedAt = table.Column<string>(type: "TEXT", nullable: true),
EventId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Galleries", x => x.Id);
table.ForeignKey(
name: "FK_Galleries_Events_EventId",
column: x => x.EventId,
principalTable: "Events",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Galleries_Users_CreatedBy",
column: x => x.CreatedBy,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "PhotoUserBuyers",
columns: table => new
{
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
UserId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PhotoUserBuyers", x => new { x.PhotoId, x.UserId });
table.ForeignKey(
name: "FK_PhotoUserBuyers_Photos_PhotoId",
column: x => x.PhotoId,
principalTable: "Photos",
principalColumn: "Id");
table.ForeignKey(
name: "FK_PhotoUserBuyers_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "UserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "TEXT", nullable: false),
RoleId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_UserRoles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id");
table.ForeignKey(
name: "FK_UserRoles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "GalleryPhotos",
columns: table => new
{
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
PhotoId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GalleryPhotos", x => new { x.GalleryId, x.PhotoId });
table.ForeignKey(
name: "FK_GalleryPhotos_Galleries_GalleryId",
column: x => x.GalleryId,
principalTable: "Galleries",
principalColumn: "Id");
table.ForeignKey(
name: "FK_GalleryPhotos_Photos_PhotoId",
column: x => x.PhotoId,
principalTable: "Photos",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "GalleryTags",
columns: table => new
{
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
TagId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GalleryTags", x => new { x.GalleryId, x.TagId });
table.ForeignKey(
name: "FK_GalleryTags_Galleries_GalleryId",
column: x => x.GalleryId,
principalTable: "Galleries",
principalColumn: "Id");
table.ForeignKey(
name: "FK_GalleryTags_Tags_TagId",
column: x => x.TagId,
principalTable: "Tags",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "GalleryUserViewers",
columns: table => new
{
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
UserId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GalleryUserViewers", x => new { x.GalleryId, x.UserId });
table.ForeignKey(
name: "FK_GalleryUserViewers_Galleries_GalleryId",
column: x => x.GalleryId,
principalTable: "Galleries",
principalColumn: "Id");
table.ForeignKey(
name: "FK_GalleryUserViewers_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_EventTags_TagId",
table: "EventTags",
column: "TagId");
migrationBuilder.CreateIndex(
name: "IX_Galleries_CreatedBy",
table: "Galleries",
column: "CreatedBy");
migrationBuilder.CreateIndex(
name: "IX_Galleries_EventId",
table: "Galleries",
column: "EventId");
migrationBuilder.CreateIndex(
name: "IX_GalleryPhotos_PhotoId",
table: "GalleryPhotos",
column: "PhotoId");
migrationBuilder.CreateIndex(
name: "IX_GalleryTags_TagId",
table: "GalleryTags",
column: "TagId");
migrationBuilder.CreateIndex(
name: "IX_GalleryUserViewers_UserId",
table: "GalleryUserViewers",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Persons_SocialMediaId",
table: "Persons",
column: "SocialMediaId");
migrationBuilder.CreateIndex(
name: "IX_PhotoPersons_PersonId",
table: "PhotoPersons",
column: "PersonId");
migrationBuilder.CreateIndex(
name: "IX_Photos_CreatedBy",
table: "Photos",
column: "CreatedBy");
migrationBuilder.CreateIndex(
name: "IX_Photos_EventId",
table: "Photos",
column: "EventId");
migrationBuilder.CreateIndex(
name: "IX_PhotoTags_TagId",
table: "PhotoTags",
column: "TagId");
migrationBuilder.CreateIndex(
name: "IX_PhotoUserBuyers_UserId",
table: "PhotoUserBuyers",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_RolePermissions_PermissionId",
table: "RolePermissions",
column: "PermissionId");
migrationBuilder.CreateIndex(
name: "IX_Roles_BaseRoleModelId",
table: "Roles",
column: "BaseRoleModelId");
migrationBuilder.CreateIndex(
name: "IX_Tags_Name",
table: "Tags",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserRoles_RoleId",
table: "UserRoles",
column: "RoleId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "__EFMigrationsLock");
migrationBuilder.DropTable(
name: "EventTags");
migrationBuilder.DropTable(
name: "GalleryPhotos");
migrationBuilder.DropTable(
name: "GalleryTags");
migrationBuilder.DropTable(
name: "GalleryUserViewers");
migrationBuilder.DropTable(
name: "PhotoPersons");
migrationBuilder.DropTable(
name: "PhotoTags");
migrationBuilder.DropTable(
name: "PhotoUserBuyers");
migrationBuilder.DropTable(
name: "Rankings");
migrationBuilder.DropTable(
name: "RolePermissions");
migrationBuilder.DropTable(
name: "UserRoles");
migrationBuilder.DropTable(
name: "Galleries");
migrationBuilder.DropTable(
name: "Tags");
migrationBuilder.DropTable(
name: "Photos");
migrationBuilder.DropTable(
name: "Permissions");
migrationBuilder.DropTable(
name: "Roles");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "Events");
migrationBuilder.DropTable(
name: "Persons");
migrationBuilder.DropTable(
name: "SocialMedia");
}
}
}

View File

@@ -0,0 +1,749 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using back.persistance.data;
#nullable disable
namespace back.Migrations
{
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("EventTag", b =>
{
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("EventId", "TagId");
b.HasIndex("TagId");
b.ToTable("EventTags", (string)null);
});
modelBuilder.Entity("GalleryPhoto", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "PhotoId");
b.HasIndex("PhotoId");
b.ToTable("GalleryPhotos", (string)null);
});
modelBuilder.Entity("GalleryTag", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "TagId");
b.HasIndex("TagId");
b.ToTable("GalleryTags", (string)null);
});
modelBuilder.Entity("GalleryUserViewer", b =>
{
b.Property<string>("GalleryId")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("GalleryId", "UserId");
b.HasIndex("UserId");
b.ToTable("GalleryUserViewers", (string)null);
});
modelBuilder.Entity("PhotoPerson", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("PersonId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "PersonId");
b.HasIndex("PersonId");
b.ToTable("PhotoPersons", (string)null);
});
modelBuilder.Entity("PhotoTag", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("TagId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "TagId");
b.HasIndex("TagId");
b.ToTable("PhotoTags", (string)null);
});
modelBuilder.Entity("PhotoUserBuyer", b =>
{
b.Property<string>("PhotoId")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("PhotoId", "UserId");
b.HasIndex("UserId");
b.ToTable("PhotoUserBuyers", (string)null);
});
modelBuilder.Entity("RolePermission", b =>
{
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.Property<string>("PermissionId")
.HasColumnType("TEXT");
b.HasKey("RoleId", "PermissionId");
b.HasIndex("PermissionId");
b.ToTable("RolePermissions", (string)null);
});
modelBuilder.Entity("UserRole", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("back.DataModels.EfmigrationsLock", b =>
{
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<string>("Timestamp")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("__EFMigrationsLock", (string)null);
});
modelBuilder.Entity("back.DataModels.Event", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasColumnType("TEXT");
b.Property<string>("Date")
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<string>("Location")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Events");
});
modelBuilder.Entity("back.DataModels.Gallery", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<int?>("IsArchived")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<int?>("IsFavorite")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsPublic")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<string>("Title")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("EventId");
b.ToTable("Galleries");
});
modelBuilder.Entity("back.DataModels.Permission", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Permissions");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Avatar")
.HasColumnType("TEXT");
b.Property<string>("Bio")
.HasMaxLength(250)
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeletedAt")
.HasColumnType("TEXT");
b.Property<int>("IsDeleted")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("ProfilePicture")
.HasColumnType("TEXT");
b.Property<string>("SocialMediaId")
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SocialMediaId");
b.ToTable("Persons");
});
modelBuilder.Entity("back.DataModels.Photo", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EventId")
.HasColumnType("TEXT");
b.Property<string>("Extension")
.HasColumnType("TEXT");
b.Property<string>("HighResUrl")
.HasColumnType("TEXT");
b.Property<int?>("IsArchived")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsFavorite")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<int?>("IsPublic")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<string>("LowResUrl")
.HasColumnType("TEXT");
b.Property<string>("MidResUrl")
.HasColumnType("TEXT");
b.Property<string>("RankingId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("EventId");
b.ToTable("Photos");
});
modelBuilder.Entity("back.DataModels.Ranking", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("DownVotes")
.HasColumnType("INTEGER");
b.Property<int>("TotalVotes")
.HasColumnType("INTEGER");
b.Property<int>("UpVotes")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Rankings");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("BaseRoleModelId")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(250)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("BaseRoleModelId");
b.ToTable("Roles");
});
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("BlueSky")
.HasColumnType("TEXT");
b.Property<string>("Discord")
.HasColumnType("TEXT");
b.Property<string>("Facebook")
.HasColumnType("TEXT");
b.Property<string>("Instagram")
.HasColumnType("TEXT");
b.Property<string>("Linkedin")
.HasColumnType("TEXT");
b.Property<string>("Other")
.HasColumnType("TEXT");
b.Property<string>("Pinterest")
.HasColumnType("TEXT");
b.Property<string>("Reddit")
.HasColumnType("TEXT");
b.Property<string>("Tiktok")
.HasColumnType("TEXT");
b.Property<string>("Twitter")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("SocialMedia");
});
modelBuilder.Entity("back.DataModels.Tag", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(25)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex(new[] { "Name" }, "IX_Tags_Name")
.IsUnique();
b.ToTable("Tags");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("EventTag", b =>
{
b.HasOne("back.DataModels.Event", null)
.WithMany()
.HasForeignKey("EventId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("GalleryPhoto", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
});
modelBuilder.Entity("GalleryTag", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("GalleryUserViewer", b =>
{
b.HasOne("back.DataModels.Gallery", null)
.WithMany()
.HasForeignKey("GalleryId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("PhotoPerson", b =>
{
b.HasOne("back.DataModels.Person", null)
.WithMany()
.HasForeignKey("PersonId")
.IsRequired();
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
});
modelBuilder.Entity("PhotoTag", b =>
{
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
b.HasOne("back.DataModels.Tag", null)
.WithMany()
.HasForeignKey("TagId")
.IsRequired();
});
modelBuilder.Entity("PhotoUserBuyer", b =>
{
b.HasOne("back.DataModels.Photo", null)
.WithMany()
.HasForeignKey("PhotoId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("RolePermission", b =>
{
b.HasOne("back.DataModels.Permission", null)
.WithMany()
.HasForeignKey("PermissionId")
.IsRequired();
b.HasOne("back.DataModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.IsRequired();
});
modelBuilder.Entity("UserRole", b =>
{
b.HasOne("back.DataModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.IsRequired();
b.HasOne("back.DataModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.IsRequired();
});
modelBuilder.Entity("back.DataModels.Gallery", b =>
{
b.HasOne("back.DataModels.User", "CreatedByNavigation")
.WithMany("Galleries")
.HasForeignKey("CreatedBy")
.IsRequired();
b.HasOne("back.DataModels.Event", "Event")
.WithMany("Galleries")
.HasForeignKey("EventId");
b.Navigation("CreatedByNavigation");
b.Navigation("Event");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.HasOne("back.DataModels.SocialMedia", "SocialMedia")
.WithMany("People")
.HasForeignKey("SocialMediaId");
b.Navigation("SocialMedia");
});
modelBuilder.Entity("back.DataModels.Photo", b =>
{
b.HasOne("back.DataModels.Person", "CreatedByNavigation")
.WithMany("Photos")
.HasForeignKey("CreatedBy")
.IsRequired();
b.HasOne("back.DataModels.Event", "Event")
.WithMany("Photos")
.HasForeignKey("EventId");
b.Navigation("CreatedByNavigation");
b.Navigation("Event");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.HasOne("back.DataModels.Role", "BaseRoleModel")
.WithMany("InverseBaseRoleModel")
.HasForeignKey("BaseRoleModelId");
b.Navigation("BaseRoleModel");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.HasOne("back.DataModels.Person", "IdNavigation")
.WithOne("User")
.HasForeignKey("back.DataModels.User", "Id")
.IsRequired();
b.Navigation("IdNavigation");
});
modelBuilder.Entity("back.DataModels.Event", b =>
{
b.Navigation("Galleries");
b.Navigation("Photos");
});
modelBuilder.Entity("back.DataModels.Person", b =>
{
b.Navigation("Photos");
b.Navigation("User");
});
modelBuilder.Entity("back.DataModels.Role", b =>
{
b.Navigation("InverseBaseRoleModel");
});
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
{
b.Navigation("People");
});
modelBuilder.Entity("back.DataModels.User", b =>
{
b.Navigation("Galleries");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,200 @@
-- Tabla de redes sociales (SocialMedia) y relación uno a uno con Person
CREATE TABLE IF NOT EXISTS SocialMedia (
Id TEXT PRIMARY KEY,
Facebook TEXT,
Instagram TEXT,
Twitter TEXT,
BlueSky TEXT,
Tiktok TEXT,
Linkedin TEXT,
Pinterest TEXT,
Discord TEXT,
Reddit TEXT,
Other TEXT
);
-- Person: cada persona tiene un grupo de redes sociales (uno a uno, fk opcional)
CREATE TABLE IF NOT EXISTS Persons (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL,
ProfilePicture TEXT,
Avatar TEXT,
SocialMediaId TEXT,
Bio TEXT,
CreatedAt TEXT NOT NULL,
UpdatedAt TEXT,
FOREIGN KEY (SocialMediaId) REFERENCES SocialMedia(Id)
);
-- User: es una persona (herencia por clave primaria compartida)
CREATE TABLE IF NOT EXISTS Users (
Id TEXT PRIMARY KEY, -- MISMA clave y valor que Persons.Id
Email TEXT NOT NULL,
Password TEXT NOT NULL,
Salt TEXT NOT NULL,
FOREIGN KEY (Id) REFERENCES Persons(Id)
);
-- Un usuario puede ver muchas galerías (muchos-a-muchos: Galleries <-> Users)
CREATE TABLE IF NOT EXISTS GalleryUserViewers (
GalleryId TEXT NOT NULL,
UserId TEXT NOT NULL,
PRIMARY KEY (GalleryId, UserId),
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
FOREIGN KEY (UserId) REFERENCES Users(Id)
);
-- Un usuario ha creado muchas galerías (uno a muchos)
-- Una galería solo puede ser creada por un usuario
CREATE TABLE IF NOT EXISTS Galleries (
Id TEXT PRIMARY KEY,
Title TEXT,
Description TEXT,
CreatedAt TEXT,
UpdatedAt TEXT,
CreatedBy TEXT NOT NULL, -- FK a Users
IsPublic INTEGER DEFAULT 1,
IsArchived INTEGER DEFAULT 0,
IsFavorite INTEGER DEFAULT 0,
EventId TEXT, -- FK opcional a Events (una galería puede asociarse a un evento)
FOREIGN KEY (CreatedBy) REFERENCES Users(Id),
FOREIGN KEY (EventId) REFERENCES Events(Id)
);
-- Galería-Photo: una galería contiene muchas imagenes, una imagen puede estar en muchas galerías (muchos-a-muchos)
CREATE TABLE IF NOT EXISTS GalleryPhotos (
GalleryId TEXT NOT NULL,
PhotoId TEXT NOT NULL,
PRIMARY KEY (GalleryId, PhotoId),
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
FOREIGN KEY (PhotoId) REFERENCES Photos(Id)
);
-- Tabla de eventos
CREATE TABLE IF NOT EXISTS Events (
Id TEXT PRIMARY KEY,
Title TEXT NOT NULL,
Description TEXT,
Date TEXT,
Location TEXT,
CreatedAt TEXT NOT NULL,
UpdatedAt TEXT NOT NULL,
CreatedBy TEXT,
UpdatedBy TEXT,
IsDeleted INTEGER NOT NULL DEFAULT 0,
DeletedAt TEXT
);
-- Tabla de fotos
CREATE TABLE IF NOT EXISTS Photos (
Id TEXT PRIMARY KEY,
Title TEXT NOT NULL,
Description TEXT,
Extension TEXT,
LowResUrl TEXT,
MidResUrl TEXT,
HighResUrl TEXT,
CreatedAt TEXT,
UpdatedAt TEXT,
CreatedBy TEXT NOT NULL, -- Persona que subió la foto: FK a Persons
UpdatedBy TEXT,
EventId TEXT, -- Una photo solo puede tener un evento asociado (FK)
RankingId TEXT,
IsFavorite INTEGER DEFAULT 0,
IsPublic INTEGER DEFAULT 1,
IsArchived INTEGER DEFAULT 0,
FOREIGN KEY (CreatedBy) REFERENCES Persons(Id),
FOREIGN KEY (EventId) REFERENCES Events(Id)
);
-- Una persona puede salir en muchas fotos, y una foto puede tener muchas personas (muchos-a-muchos)
CREATE TABLE IF NOT EXISTS PhotoPersons (
PhotoId TEXT NOT NULL,
PersonId TEXT NOT NULL,
PRIMARY KEY (PhotoId, PersonId),
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
FOREIGN KEY (PersonId) REFERENCES Persons(Id)
);
-- Un usuario puede comprar muchas fotos para verlas, y una foto puede haber sido comprada por muchos usuarios
-- (solo necesario si IsPublic = 0)
CREATE TABLE IF NOT EXISTS PhotoUserBuyers (
PhotoId TEXT NOT NULL,
UserId TEXT NOT NULL,
PRIMARY KEY (PhotoId, UserId),
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
FOREIGN KEY (UserId) REFERENCES Users(Id)
);
-- Tabla de tags (únicos)
CREATE TABLE IF NOT EXISTS Tags (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL UNIQUE,
CreatedAt TEXT NOT NULL
);
-- Una foto puede tener muchos tags (muchos-a-muchos)
CREATE TABLE IF NOT EXISTS PhotoTags (
PhotoId TEXT NOT NULL,
TagId TEXT NOT NULL,
PRIMARY KEY (PhotoId, TagId),
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
FOREIGN KEY (TagId) REFERENCES Tags(Id)
);
-- Un evento puede tener muchos tags (muchos-a-muchos)
CREATE TABLE IF NOT EXISTS EventTags (
EventId TEXT NOT NULL,
TagId TEXT NOT NULL,
PRIMARY KEY (EventId, TagId),
FOREIGN KEY (EventId) REFERENCES Events(Id),
FOREIGN KEY (TagId) REFERENCES Tags(Id)
);
-- Una galería puede tener muchos tags (muchos-a-muchos)
CREATE TABLE IF NOT EXISTS GalleryTags (
GalleryId TEXT NOT NULL,
TagId TEXT NOT NULL,
PRIMARY KEY (GalleryId, TagId),
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
FOREIGN KEY (TagId) REFERENCES Tags(Id)
);
-- Rankings (por si corresponde)
CREATE TABLE IF NOT EXISTS Rankings (
Id TEXT PRIMARY KEY,
TotalVotes INTEGER NOT NULL,
UpVotes INTEGER NOT NULL,
DownVotes INTEGER NOT NULL
);
-- Permissions y Roles, tal y como en el mensaje anterior...
CREATE TABLE IF NOT EXISTS Permissions (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL,
Description TEXT
);
CREATE TABLE IF NOT EXISTS Roles (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL,
Description TEXT,
BaseRoleModelId TEXT,
FOREIGN KEY (BaseRoleModelId) REFERENCES Roles(Id)
);
CREATE TABLE IF NOT EXISTS UserRoles (
UserId TEXT NOT NULL,
RoleId TEXT NOT NULL,
PRIMARY KEY (UserId, RoleId),
FOREIGN KEY (UserId) REFERENCES Users(Id),
FOREIGN KEY (RoleId) REFERENCES Roles(Id)
);
CREATE TABLE IF NOT EXISTS RolePermissions (
RoleId TEXT NOT NULL,
PermissionId TEXT NOT NULL,
PRIMARY KEY (RoleId, PermissionId),
FOREIGN KEY (RoleId) REFERENCES Roles(Id),
FOREIGN KEY (PermissionId) REFERENCES Permissions(Id)
);

View File

@@ -0,0 +1,28 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class EventRelationEstablisher: IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>(entity =>
{
entity.HasMany(d => d.Tags).WithMany(p => p.Events)
.UsingEntity<Dictionary<string, object>>(
"EventTag",
r => r.HasOne<Tag>().WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Event>().WithMany()
.HasForeignKey("EventId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("EventId", "TagId");
j.ToTable("EventTags");
});
});
}
}

View File

@@ -0,0 +1,68 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class GalleryRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Gallery>(entity =>
{
entity.Property(e => e.IsArchived).HasDefaultValue(0);
entity.Property(e => e.IsFavorite).HasDefaultValue(0);
entity.Property(e => e.IsPublic).HasDefaultValue(1);
entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.Galleries)
.HasForeignKey(d => d.CreatedBy)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasOne(d => d.Event).WithMany(p => p.Galleries).HasForeignKey(d => d.EventId);
entity.HasMany(d => d.Photos).WithMany(p => p.Galleries)
.UsingEntity<Dictionary<string, object>>(
"GalleryPhoto",
r => r.HasOne<Photo>().WithMany()
.HasForeignKey("PhotoId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Gallery>().WithMany()
.HasForeignKey("GalleryId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("GalleryId", "PhotoId");
j.ToTable("GalleryPhotos");
});
entity.HasMany(d => d.Tags).WithMany(p => p.Galleries)
.UsingEntity<Dictionary<string, object>>(
"GalleryTag",
r => r.HasOne<Tag>().WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Gallery>().WithMany()
.HasForeignKey("GalleryId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("GalleryId", "TagId");
j.ToTable("GalleryTags");
});
entity.HasMany(d => d.Users).WithMany(p => p.GalleriesNavigation)
.UsingEntity<Dictionary<string, object>>(
"GalleryUserViewer",
r => r.HasOne<User>().WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Gallery>().WithMany()
.HasForeignKey("GalleryId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("GalleryId", "UserId");
j.ToTable("GalleryUserViewers");
});
});
}
}

View File

@@ -0,0 +1,8 @@
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public interface IRelationEstablisher
{
void EstablishRelation(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,68 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class PersonRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Photo>(entity =>
{
entity.Property(e => e.IsArchived).HasDefaultValue(0);
entity.Property(e => e.IsFavorite).HasDefaultValue(0);
entity.Property(e => e.IsPublic).HasDefaultValue(1);
entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.Photos)
.HasForeignKey(d => d.CreatedBy)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasOne(d => d.Event).WithMany(p => p.Photos).HasForeignKey(d => d.EventId);
entity.HasMany(d => d.People).WithMany(p => p.PhotosNavigation)
.UsingEntity<Dictionary<string, object>>(
"PhotoPerson",
r => r.HasOne<Person>().WithMany()
.HasForeignKey("PersonId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Photo>().WithMany()
.HasForeignKey("PhotoId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("PhotoId", "PersonId");
j.ToTable("PhotoPersons");
});
entity.HasMany(d => d.Tags).WithMany(p => p.Photos)
.UsingEntity<Dictionary<string, object>>(
"PhotoTag",
r => r.HasOne<Tag>().WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Photo>().WithMany()
.HasForeignKey("PhotoId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("PhotoId", "TagId");
j.ToTable("PhotoTags");
});
entity.HasMany(d => d.Users).WithMany(p => p.Photos)
.UsingEntity<Dictionary<string, object>>(
"PhotoUserBuyer",
r => r.HasOne<User>().WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Photo>().WithMany()
.HasForeignKey("PhotoId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("PhotoId", "UserId");
j.ToTable("PhotoUserBuyers");
});
});
}
}

View File

@@ -0,0 +1,307 @@
//using back.DataModels;
//using back.DTO;
//using back.persistance.blob;
//using back.services.ImageResizer;
//using Microsoft.EntityFrameworkCore;
//using Microsoft.Extensions.Hosting.Internal;
//namespace back.persistance.data.relations;
//public class PhotoContext : DbContext
//{
// private readonly IImageResizer _Resizer;
// private readonly IBlobStorageService _BlobStorage;
// private readonly TagContext _tagContext;
// private readonly EventContext _eventContext;
// private readonly PersonRelationEstablisher _personContext;
// public PhotoContext(DbContextOptions<PhotoContext> options, IHostEnvironment hostingEnvironment,
// IImageResizer resizer,
// IBlobStorageService blobStorage,
// TagContext tags,
// EventContext events,
// PersonRelationEstablisher persons
// ) : base(options)
// {
// _Resizer = resizer;
// _BlobStorage = blobStorage;
// _tagContext = tags;
// _eventContext = events;
// _personContext = persons;
// if (hostingEnvironment.IsDevelopment())
// {
// Database.EnsureCreated();
// }
// else
// {
// Database.Migrate();
// }
// }
// protected override void OnModelCreating(ModelBuilder modelBuilder)
// {
// // Photo -> Tags (muchos-a-muchos)
// modelBuilder.Entity<Photo>()
// .HasMany(p => p.Tags)
// .WithMany(t => t.Photos)
// .UsingEntity(j => j.ToTable("PhotoTags"));
// // Photo -> Persons (muchos-a-muchos)
// modelBuilder.Entity<Photo>()
// .HasMany(p => p.PersonsIn)
// .WithMany(per => per.Photos)
// .UsingEntity(j => j.ToTable("PhotoPersons"));
// // Photo -> Event (muchos-a-uno)
// modelBuilder.Entity<Photo>()
// .HasOne(p => p.Event)
// .WithMany() // Un evento puede tener múltiples fotos
// .HasForeignKey(p => p.EventId);
// // Photo -> Ranking (uno-a-uno)
// modelBuilder.Entity<Photo>()
// .HasOne(p => p.Ranking)
// .WithOne(r => r.Photo) // Un ranking está asociado a una sola foto
// .HasForeignKey<Photo>(p => p.RankingId);
// base.OnModelCreating(modelBuilder);
// }
// public async Task CreateNew(PhotoFormModel? form)
// {
// if (form == null) { return; }
// var photo = new Photo(
// Guid.NewGuid().ToString(),
// form.Title,
// form.Description ?? string.Empty,
// string.Empty, // LowResUrl will be set later
// string.Empty, // MidResUrl will be set later
// string.Empty, // HighResUrl will be set later
// DateTime.UtcNow,
// DateTime.UtcNow,
// form.UserId,
// form.UserId
// )
// {
// IsPublic = form.IsPublic
// };
// List<Task> tasks = [
// SaveBlob(photo, form),
// LinkTags(photo, form.Tags ?? [], form.UserId),
// LinkEvent(photo, form.Evento ?? "", form.UserId),
// LinkPersons(photo, form.People ?? [], form.UserId),
// ];
// await Task.WhenAll(tasks);
// await Photos.AddAsync(photo);
// await SaveChangesAsync();
// }
// private async Task LinkPersons(Photo photo, string[] personas, string updatedBy = "SYSTEM")
// {
// if (photo == null || personas == null || personas.Length == 0) return;
// foreach (var personId in personas)
// {
// var person = await _personContext.GetById(personId);
// if (person != null)
// {
// await LinkPersons(photo, person, updatedBy);
// }
// }
// }
// private async Task LinkPersons(Photo photo, Person tag, string updatedBy = "SYSTEM")
// {
// if (tag == null) return;
// // Ensure the tag exists
// if (await _personContext.Exists(tag.Id))
// {
// photo.PersonsIn ??= [];
// photo.PersonsIn.Add(tag);
// photo.UpdatedAt = DateTime.UtcNow;
// photo.UpdatedBy = updatedBy; // or use a more appropriate value
// }
// }
// private async Task LinkTags(Photo photo, string[] tags, string updatedBy = "SYSTEM")
// {
// if (photo == null || tags == null || tags.Length == 0) return;
// foreach (var tagId in tags)
// {
// var tag = await _tagContext.GetById(tagId);
// if (tag != null)
// {
// await LinkTag(photo, tag, updatedBy);
// }
// }
// }
// private async Task LinkTag(Photo photo, Tag tag, string updatedBy = "SYSTEM")
// {
// if (tag == null) return;
// // Ensure the tag exists
// if (await _tagContext.Exists(tag.Id))
// {
// photo.Tags.Add(tag);
// photo.UpdatedAt = DateTime.UtcNow;
// photo.UpdatedBy = updatedBy; // or use a more appropriate value
// }
// }
// private async Task LinkEvent(Photo photo, string eventId, string updatedBy = "SYSTEM")
// {
// if (string.IsNullOrEmpty(eventId)) return;
// var evento = await _eventContext.GetById(eventId);
// if (evento != null)
// {
// await LinkEvent(photo, evento, updatedBy);
// }
// }
// private async Task LinkEvent(Photo photo, Event? evento, string updatedBy = "SYSTEM")
// {
// if (evento == null) return;
// // Ensure the event exists
// if (await _eventContext.Exists(evento.Id))
// {
// photo.Event = evento;
// photo.UpdatedAt = DateTime.UtcNow;
// photo.UpdatedBy = updatedBy;
// }
// }
// private async Task SaveBlob(Photo photo, PhotoFormModel form)
// {
// if (form.Image != null && form.Image.Length > 0)
// {
// var lowRes = await _Resizer.ResizeImage(form.Image, 480);
// var midRes = await _Resizer.ResizeImage(form.Image, 720);
// // Upload images to blob storage
// photo.Extension = form.Image.FileName.Split('.').Last();
// photo.LowResUrl = $"low/{photo.Id}.webp";
// photo.MidResUrl = $"mid/{photo.Id}.webp";
// photo.HighResUrl = $"high/{photo.Id}.{photo.Extension}";
// await _BlobStorage.SaveAsync(lowRes, photo.LowResUrl);
// await _BlobStorage.SaveAsync(midRes, photo.MidResUrl);
// await _BlobStorage.SaveAsync(form.Image.OpenReadStream(), photo.HighResUrl);
// }
// }
// public async Task<Photo?> GetById(string id)
// {
// return await GetById(Guid.Parse(id));
// }
// public async Task<Photo?> GetById(Guid id)
// {
// try
// {
// return await Photos.FindAsync(id);
// }
// catch
// {
// return null;
// }
// }
// public async Task<int> GetTotalItems()
// {
// try
// {
// return await Photos.CountAsync();
// }
// catch
// {
// return 0;
// }
// }
// public async Task<IEnumerable<Photo>?> GetPage(int page = 1, int pageSize = 20)
// {
// if (page < 1) page = 1;
// if (pageSize < 1) pageSize = 20;
// try
// {
// return await Photos
// .OrderByDescending(p => p.CreatedAt)
// .Skip((page - 1) * pageSize)
// .Take(pageSize)
// .ToListAsync();
// }
// catch
// {
// return null;
// }
// }
// public async Task<bool> Exists(Photo? photo)
// {
// try
// {
// if (photo == null) return false;
// if (string.IsNullOrEmpty(photo.Id)) return false;
// return await Photos.AnyAsync(p => p.Id == photo.Id);
// }
// catch
// {
// return false; // Handle exceptions gracefully
// }
// }
// public async Task<bool> Exists(string id)
// {
// try
// {
// if (string.IsNullOrEmpty(id)) return false;
// return await Photos.AnyAsync(p => p.Id == id);
// }
// catch
// {
// return false; // Handle exceptions gracefully
// }
// }
// public async Task Delete(Photo photo)
// {
// if (photo == null) return;
// if (await Exists(photo))
// {
// // Delete the photo from blob storage
// if (!string.IsNullOrEmpty(photo.LowResUrl))
// await _BlobStorage.DeleteAsync(photo.LowResUrl);
// if (!string.IsNullOrEmpty(photo.MidResUrl))
// await _BlobStorage.DeleteAsync(photo.MidResUrl);
// if (!string.IsNullOrEmpty(photo.HighResUrl))
// await _BlobStorage.DeleteAsync(photo.HighResUrl);
// Photos.Remove(photo);
// await SaveChangesAsync();
// }
// }
// public async Task Update(Photo photo)
// {
// if (photo == null) return;
// if (await Exists(photo))
// {
// var evento = photo.Event;
// photo.Event = null;
// await LinkEvent(photo, evento, photo.UpdatedBy);
// var tags = photo.Tags.Select(t => t.Id);
// photo.Tags.Clear();
// await LinkTags(photo, [.. tags], photo.UpdatedBy);
// var persons = photo.PersonsIn?.Select(t => t.Id) ?? [];
// photo.PersonsIn = null;
// await LinkPersons(photo, [.. persons], photo.UpdatedBy);
// Photos.Update(photo);
// await SaveChangesAsync();
// }
// }
//}

View File

@@ -0,0 +1,15 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class PhotoRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.HasOne(d => d.SocialMedia).WithMany(p => p.People).HasForeignKey(d => d.SocialMediaId);
});
}
}

View File

@@ -0,0 +1,25 @@
//using back.DataModels;
//using Microsoft.EntityFrameworkCore;
//namespace back.persistance.data.relations;
//public class RoleContext : DbContext
//{
// protected override void OnModelCreating(ModelBuilder modelBuilder)
// {
// // Role -> Permissions (muchos-a-muchos)
// modelBuilder.Entity<Role>()
// .HasMany(r => r.Permissions)
// .WithMany(p => p.Roles)
// .UsingEntity(j => j.ToTable("RolePermissions"));
// // Role -> BaseRole (auto-referencial)
// modelBuilder.Entity<Role>()
// .HasOne(r => r.BaseRoleModel)
// .WithMany() // Un rol base puede ser heredado por múltiples roles
// .HasForeignKey(r => r.BaseRoleModelId);
// base.OnModelCreating(modelBuilder);
// }
//}

View File

@@ -0,0 +1,30 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class RoleRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>(entity =>
{
entity.HasOne(d => d.BaseRoleModel).WithMany(p => p.InverseBaseRoleModel).HasForeignKey(d => d.BaseRoleModelId);
entity.HasMany(d => d.Permissions).WithMany(p => p.Roles)
.UsingEntity<Dictionary<string, object>>(
"RolePermission",
r => r.HasOne<Permission>().WithMany()
.HasForeignKey("PermissionId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<Role>().WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("RoleId", "PermissionId");
j.ToTable("RolePermissions");
});
});
}
}

View File

@@ -0,0 +1,40 @@
//using back.DataModels;
//using Microsoft.EntityFrameworkCore;
//namespace back.persistance.data.relations;
//public class SeedingDbContext : DbContext
//{
// protected override void OnModelCreating(ModelBuilder modelBuilder)
// {
// // 3. CONFIGURAR RELACIONES
// modelBuilder.Entity<Role>()
// .HasMany(r => r.Permissions)
// .WithMany(p => p.Roles)
// .UsingEntity<Dictionary<string, object>>(
// "RolePermissions",
// j => j.HasOne<Permission>().WithMany().HasForeignKey("PermissionsId"),
// j => j.HasOne<Role>().WithMany().HasForeignKey("RolesId"),
// j => j.HasData(
// // Usuario: VIEW_CONTENT y LIKE_CONTENT
// new { RolesId = "1", PermissionsId = "1" },
// new { RolesId = "1", PermissionsId = "2" },
// // Content Manager: permisos adicionales
// new { RolesId = "2", PermissionsId = "5" },
// new { RolesId = "2", PermissionsId = "3" },
// new { RolesId = "2", PermissionsId = "4" },
// new { RolesId = "2", PermissionsId = "9" },
// new { RolesId = "2", PermissionsId = "8" },
// // Admin: permisos adicionales
// new { RolesId = "3", PermissionsId = "6" },
// new { RolesId = "3", PermissionsId = "7" },
// new { RolesId = "3", PermissionsId = "10" }
// )
// );
// // Resto de configuraciones...
// base.OnModelCreating(modelBuilder);
// }
//}

View File

@@ -0,0 +1,15 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class TagRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>(entity =>
{
entity.HasIndex(e => e.Name, "IX_Tags_Name").IsUnique();
});
}
}

View File

@@ -0,0 +1,32 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.relations;
public class UserRelationEstablisher : IRelationEstablisher
{
public void EstablishRelation(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(entity =>
{
entity.HasOne(d => d.IdNavigation).WithOne(p => p.User)
.HasForeignKey<User>(d => d.Id)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasMany(d => d.Roles).WithMany(p => p.Users)
.UsingEntity<Dictionary<string, object>>(
"UserRole",
r => r.HasOne<Role>().WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.ClientSetNull),
l => l.HasOne<User>().WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.ClientSetNull),
j =>
{
j.HasKey("UserId", "RoleId");
j.ToTable("UserRoles");
});
});
}
}

View File

@@ -0,0 +1,9 @@
using back.DataModels;
using DependencyInjector.Lifetimes;
using Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IPersonRepository : IRepository<Person, string>, IScoped
{
}

View File

@@ -0,0 +1,8 @@
using back.DataModels;
using DependencyInjector.Lifetimes;
using Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IPhotoRepository : IRepository<Photo, string>, IScoped
{ }

View File

@@ -0,0 +1,14 @@
using back.DataModels;
using DependencyInjector.Lifetimes;
using Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IUserRepository : IRepository<User, string>, IScoped
{
Task<User?> GetByEmail(string email);
Task<string?> GetUserSaltByEmail(string email);
Task<User?> Login(string email, string password);
Task<bool> ExistsByEmail(string email);
//Task<bool> IsContentManager(string userId);
}

View File

@@ -0,0 +1,10 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using Transactional.Implementations.EntityFramework;
namespace back.persistance.data.repositories;
public class PersonRepository(DataContext context) : ReadWriteRepository<Person, string>(context), IPersonRepository
{
// Implement methods specific to Photo repository if needed
}

View File

@@ -0,0 +1,10 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using Transactional.Implementations.EntityFramework;
namespace back.persistance.data.repositories;
public class PhotoRepository(DataContext context) : ReadWriteRepository<Photo, string>(context), IPhotoRepository
{
// Implement methods specific to Photo repository if needed
}

View File

@@ -0,0 +1,70 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using Microsoft.EntityFrameworkCore;
using Transactional.Implementations.EntityFramework;
namespace back.persistance.data.repositories;
public class UserRepository(DataContext context) : ReadWriteRepository<User, string>(context), IUserRepository
{
public async Task<User?> GetByEmail(string email)
{
try
{
if (string.IsNullOrEmpty(email)) return null;
return await Entity.FirstOrDefaultAsync(u => u.Email == email);
}
catch
{
return null;
}
}
public async Task<string?> GetUserSaltByEmail(string email)
{
try
{
if (string.IsNullOrEmpty(email)) return string.Empty;
var user = await Entity.FirstOrDefaultAsync(u => u.Email == email);
return user?.Salt ?? string.Empty;
}
catch
{
return string.Empty;
}
}
public async Task<User?> Login(string email, string password)
{
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
try
{
return await Entity.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
catch
{
return null;
}
}
public async Task<bool> ExistsByEmail(string email)
{
try
{
if (string.IsNullOrEmpty(email)) return false;
return await Entity.AnyAsync(u => u.Email == email);
}
catch
{
return false;
}
}
//public async Task<bool> IsContentManager(string userId)
//{
// var user = await GetById(userId);
// if (user == null)
// return false;
// return user.Roles.Any(role => role.IsContentManager() || role.IsAdmin());
//}
}

View File

@@ -0,0 +1,8 @@
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.seeders;
public interface ISeeder
{
void Seed(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,23 @@
//using back.DataModels;
//using Microsoft.EntityFrameworkCore;
//namespace back.persistance.data.seeders;
//public class PermissionSeeder : ISeeder
//{
// public void Seed(ModelBuilder modelBuilder)
// {
// modelBuilder.Entity<Permission>().HasData(
// Permission.ViewContentPermission,
// Permission.LikeContentPermission,
// Permission.EditContentPermission,
// Permission.DeleteContentPermission,
// Permission.CreateContentPermission,
// Permission.EditUserPermission,
// Permission.DeleteUserPermission,
// Permission.DisableUserPermission,
// Permission.CreateUserPermission,
// Permission.EditWebConfigPermission
// );
// }
//}

View File

@@ -0,0 +1,16 @@
//using back.DataModels;
//using Microsoft.EntityFrameworkCore;
//namespace back.persistance.data.seeders;
//public class RoleSeeder : ISeeder
//{
// public void Seed(ModelBuilder modelBuilder)
// {
// modelBuilder.Entity<Permission>().HasData(
// new Role { Id = "1", Name = "User", Description = "Role for regular users", BaseRoleModelId = null },
// new Role { Id = "2", Name = "Content Manager", Description = "Role for managing content", BaseRoleModelId = "1" },
// new Role { Id = "3", Name = "Admin", Description = "Administrator role with full permissions", BaseRoleModelId = "2" }
// );
// }
//}

View File

@@ -0,0 +1,14 @@
using back.DataModels;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.seeders;
public class SystemUserSeeder : ISeeder
{
public void Seed(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Permission>().HasData(
User.SystemUser
);
}
}

View File

@@ -1,6 +0,0 @@
namespace back.services.ImageResizer;
public interface IImageResizer
{
Task<Stream> ResizeImage(IFormFile image, int v);
}

View File

@@ -0,0 +1,11 @@
using System.Net;
namespace back.services.bussines;
public static class Errors
{
public static readonly HttpErrorMap Unauthorized =
new(HttpStatusCode.Unauthorized, "Invalid user data. Email or password are wrong.");
public static readonly HttpErrorMap BadRequest =
new(HttpStatusCode.BadRequest, "Missing user data.");
}

View File

@@ -0,0 +1,5 @@
using System.Net;
namespace back.services.bussines;
public record HttpErrorMap(HttpStatusCode Code, string Description);

View File

@@ -0,0 +1,16 @@
using back.DataModels;
using back.DTO;
using DependencyInjector.Abstractions.ClassTypes;
using DependencyInjector.Lifetimes;
namespace back.services.bussines.PhotoService;
public interface IPhotoService: IScoped
{
Task<Photo?> Create(PhotoFormModel form);
Task Delete(string id, string userId = "00000000-0000-0000-0000-000000000001");
Task<Photo?> Get(string id, string userId = "00000000-0000-0000-0000-000000000001");
Task<(string? mediaType, byte[]? fileBytes)> GetBytes(string id, string res = "");
Task<(int totalItems, IEnumerable<Photo>? pageData)> GetPage(int page, int pageSize);
Task<Photo?> Update(Photo photo, string userId = "00000000-0000-0000-0000-000000000001");
}

View File

@@ -0,0 +1,82 @@
using back.DataModels;
using back.DTO;
using back.persistance.blob;
using back.persistance.data.repositories.Abstracts;
namespace back.services.bussines.PhotoService;
public class PhotoService(
IPhotoRepository photoRepository,
IUserRepository userRepository,
IBlobStorageService blobStorageService
) : IPhotoService
{
public async Task<Photo?> Create(PhotoFormModel form)
{
ArgumentNullException.ThrowIfNull(form);
if (form.Image == null || form.Image.Length == 0)
throw new ArgumentException("No image uploaded.", nameof(form));
//if (string.IsNullOrEmpty(form.UserId) || await userRepository.IsContentManager(form.UserId))
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(form.UserId));
throw new NotImplementedException();
}
public async Task Delete(string id, string userId = User.SystemUserId)
{
//if (string.IsNullOrEmpty(userId) || await userRepository.IsContentManager(userId))
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(userId));
photoRepository.Delete(id);
}
public async Task<Photo?> Get(string id, string userId = User.SystemUserId)
{
Photo? photo = await photoRepository.GetById(id);
return photo;
//return photo?.CanBeSeenBy(userId) ?? false
// ? photo
// : null;
}
public async Task<(string? mediaType, byte[]? fileBytes)> GetBytes(string id, string res = "")
{
var photo = await photoRepository.GetById(id);
if (photo == null)
return (null, null);
string filePath = res.ToLower() switch
{
"high" => photo.HighResUrl,
"mid" => photo.MidResUrl,
"low" or _ => photo.LowResUrl
};
string? mediaType = res.ToLower() switch
{
"high" => $"image/{photo.Extension}",
"mid" or "low" or _ => "image/webp",
};
return (
mediaType,
await blobStorageService.GetBytes(filePath) ?? throw new FileNotFoundException("File not found.", filePath)
);
}
public async Task<(int totalItems, IEnumerable<Photo>? pageData)> GetPage(int page, int pageSize)
{
return (
totalItems: await photoRepository.GetTotalItems(),
pageData: photoRepository.GetPage(page, pageSize)
);
}
public async Task<Photo?> Update(Photo photo, string userId = "00000000-0000-0000-0000-000000000001")
{
//if (string.IsNullOrEmpty(userId) || await userRepository.IsContentManager(userId))
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(userId));
return await photoRepository.Update(photo);
}
}

View File

@@ -0,0 +1,14 @@
using back.DataModels;
using DependencyInjector.Abstractions.ClassTypes;
using DependencyInjector.Lifetimes;
namespace back.services.bussines.UserService;
public interface IUserService: IScoped
{
Task<User?> Create(string clientId, User user);
Task<User?> Login(string email, string password, string clientId);
Task SendResetPassword(string email);
Task<User?> Update(User user);
Task<User?> ValidateSystemUser(string email, string password, string systemKey, string clientId);
}

View File

@@ -0,0 +1,133 @@
using back.DataModels;
using back.persistance.blob;
using back.persistance.data.repositories.Abstracts;
using back.services.engine.Crypto;
using back.services.engine.mailing;
using System.Text;
namespace back.services.bussines.UserService;
public class UserService(
IUserRepository userRepository, ICryptoService cryptoService,
IEmailService emailService,
IBlobStorageService blobStorageService
) : IUserService
{
private readonly IUserRepository _repository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
private readonly ICryptoService _cryptoService = cryptoService;
private readonly IEmailService _emailService = emailService;
private readonly IBlobStorageService _blobStorageService = blobStorageService;
public async Task<User?> Create(string clientId, User user)
{
ArgumentNullException.ThrowIfNull(user);
if (user.Id != null && await _repository.Exists(user.Id))
{
return await _repository.GetById(user.Id);
}
if (string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
{
return null;
}
if (await _repository.Exists(user.Email))
{
return await _repository.GetByEmail(user.Email);
}
if (string.IsNullOrEmpty(user.Salt))
{
user.Salt = _cryptoService.Salt();
}
user.Password = _cryptoService.Decrypt(clientId, user.Password) ?? string.Empty;
user.Password = _cryptoService.HashPassword(user.Password, user.Salt) ?? string.Empty;
user.CreatedAt = DateTimeOffset.UtcNow.ToString("dd-MM-yyyy HH:mm:ss zz");
//user.Roles.Add(Role.UserRole);
await _repository.Insert(user);
await _repository.SaveChanges();
return user;
}
public async Task<User?> Update(User user)
{
ArgumentNullException.ThrowIfNull(user);
if (user.Id == null || !await _repository.Exists(user.Id))
{
return null;
}
var existingUser = await _repository.GetById(user.Id);
if (existingUser == null) return null;
existingUser.Email = user.Email;
await _repository.Update(existingUser);
await _repository.SaveChanges();
return existingUser;
}
public async Task<User?> Login(string email, string password, string clientId)
{
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
try
{
var decryptedPass = _cryptoService.Decrypt(clientId, password);
var salt = await _repository.GetUserSaltByEmail(email);
var hashedPassword = _cryptoService.HashPassword(decryptedPass, salt);
var user = await _repository.Login(email, hashedPassword ?? string.Empty);
return user;
}
catch
{
return null;
}
}
public async Task SendResetPassword(string email)
{
var exists = await _repository.ExistsByEmail(email);
if (!exists)
{
return;
}
await _emailService.SendEmailAsync(
tos: email,
from: "admin@mmorales.photo",
subject: "Reset Password",
body: "If you received this email, it means that you have requested a password reset. Please follow the instructions in the email to reset your password."
);
}
public async Task<User?> ValidateSystemUser(string email, string password, string systemKey, string clientId)
{
password = _cryptoService.Decrypt(clientId, password) ?? string.Empty;
systemKey = _cryptoService.Decrypt(clientId, systemKey) ?? string.Empty;
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(systemKey))
{
return null;
}
if (!email.Equals("@system", StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
var systemKeyBytes = await _blobStorageService.GetBytes("systemkey.lock");
var systemKeyString = Encoding.UTF8.GetString(systemKeyBytes ?? []);
var systemKeyObject = System.Text.Json.JsonSerializer.Deserialize<SystemKey>(systemKeyString);
if (systemKeyObject == null || !systemKeyObject.IsValid(email, password, systemKey))
{
return null;
}
if (!await _repository.ExistsByEmail(email))
{
return null;
}
var user = await _repository.GetByEmail(email);
if (user == null)
{
return null;
}
return await Login(user.Email!, user.Password!, clientId);
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.Caching.Memory;
using System.Security.Cryptography;
namespace back.services.Crypto;
namespace back.services.engine.Crypto;
public class CryptoService(IMemoryCache cache) : ICryptoService
{
@@ -28,7 +28,7 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
}
};
public string? Encrypt(string plainText, string clientId)
public string? Encrypt(string clientId,string plainText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
@@ -41,7 +41,7 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
}
// import rsa keys and configure RSA for encryption
using var rsa = RSA.Create(2048);
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicCert), out _);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Encrypt the plain text using RSA
string? encryptedText = null;
@@ -59,7 +59,7 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
return encryptedText;
}
public string? Decrypt(string encryptedText, string clientId)
public string? Decrypt(string clientId, string encryptedText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
@@ -72,7 +72,7 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
}
// import rsa keys and configure RSA for decryption
using var rsa = RSA.Create(2048);
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicCert), out _);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Decrypt the encrypted text using RSA
string? plainText = null;
@@ -96,8 +96,9 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
{
return publicCert;
}
(publicCert, _) = GenerateCertificate();
(publicCert, string privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return publicCert;
}
@@ -107,7 +108,8 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
{
return privateCert;
}
(_, privateCert) = GenerateCertificate();
(string publicCert, privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return privateCert;
}
@@ -116,7 +118,7 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
{
// Generate a new RSA key pair for the client
using var rsa = RSA.Create(2048);
var publicKey = rsa.ExportRSAPublicKey();
var publicKey = rsa.ExportSubjectPublicKeyInfo();
var privateKey = rsa.ExportRSAPrivateKey();
// Convert to Base64 strings for storage
var publicCert = Convert.ToBase64String(publicKey);
@@ -165,4 +167,9 @@ public class CryptoService(IMemoryCache cache) : ICryptoService
rng.GetBytes(saltBytes);
return Convert.ToBase64String(saltBytes);
}
public string? HashPassword(string plainPassword, string plainSalt)
{
return Hash($"{plainPassword}{plainSalt}{Pepper()}");
}
}

View File

@@ -1,10 +1,13 @@
namespace back.services.Crypto;
using DependencyInjector.Lifetimes;
public interface ICryptoService
namespace back.services.engine.Crypto;
public interface ICryptoService : ISingleton
{
string? Encrypt(string clientId, string plainText);
string? Decrypt(string clientId, string encryptedText);
string? Hash(string plainText);
string? HashPassword(string? plainPassword, string? plainSalt);
bool VerifyHash(string plainText, string hash);
string Salt();
string Pepper();

View File

@@ -0,0 +1,8 @@
using DependencyInjector.Lifetimes;
namespace back.services.engine.ImageResizer;
public interface IImageResizer : ISingleton
{
Task<Stream> ResizeImage(IFormFile image, int v);
}

View File

@@ -1,7 +1,7 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
namespace back.services.ImageResizer;
namespace back.services.engine.ImageResizer;
public sealed class ImageResizer : IImageResizer
{

View File

@@ -0,0 +1,8 @@
using DependencyInjector.Lifetimes;
namespace back.services.engine.PasswordGenerator;
public interface IPasswordGenerator : ISingleton
{
string Generate(int length, bool includeNumbers = true, bool includeMayus = true, bool includeMinus = true, bool includeSpecials = true);
}

View File

@@ -0,0 +1,24 @@
namespace back.services.engine.PasswordGenerator;
public class PasswordGenerator : IPasswordGenerator
{
public string Generate(int length, bool includeNumbers = true, bool includeMayus = true, bool includeMinus = true, bool includeSpecials = true)
{
const string numbers = "0123456789";
const string mayus = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string minus = "abcdefghijklmnopqrstuvwxyz";
const string specials = "!@#$%^&*()_+[]{}|;:,.<>?";
var characters = minus;
if (includeNumbers) characters += numbers;
if (includeMayus) characters += mayus;
if (includeSpecials) characters += specials;
var random = new Random((int)DateTimeOffset.UtcNow.Ticks);
var password = new char[length];
for (int i = 0; i < length; i++)
{
password[i] = characters[random.Next(characters.Length)];
}
return new string(password);
}
}

View File

@@ -0,0 +1,8 @@
using DependencyInjector.Lifetimes;
namespace back.services.engine.SystemUser;
public interface ISystemUserGenerator: IScoped
{
Task GenerateAsync();
}

View File

@@ -0,0 +1,47 @@
using back.DataModels;
using back.persistance.blob;
using back.persistance.data;
using back.persistance.data.repositories.Abstracts;
using back.services.engine.Crypto;
using back.services.engine.PasswordGenerator;
using Transactional.Abstractions.Interfaces;
namespace back.services.engine.SystemUser;
public class SystemUserGenerator(
ITransactionalService<DataContext> transactional,
IUserRepository userRepository,
IPersonRepository personRepository,
ICryptoService cryptoService,
IBlobStorageService blobStorageService,
IPasswordGenerator passwordGenerator) : ISystemUserGenerator
{
public async Task GenerateAsync()
{
var systemKey = new SystemKey() {
Password = passwordGenerator.Generate(16),
};
var systemKeyJson = System.Text.Json.JsonSerializer.Serialize(systemKey);
using Stream stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(systemKeyJson));
await blobStorageService.Delete("systemkey.lock");
await blobStorageService.Save(
stream,
"systemkey.lock"
);
User.SystemUser.Password = systemKey.Password;
User.SystemUser.Salt = cryptoService.Salt();
User.SystemUser.Password = cryptoService.HashPassword(User.SystemUser.Password, User.SystemUser.Salt) ?? string.Empty;
if (!await userRepository.Exists(User.SystemUser.Id!))
{
await transactional.DoTransaction(async () => {
await personRepository.Insert(Person.SystemPerson);
await userRepository.Insert(User.SystemUser);
});
}
}
}

View File

@@ -0,0 +1,66 @@
using back.Options;
using Microsoft.Extensions.Options;
using System.Net;
using System.Net.Mail;
namespace back.services.engine.mailing;
public class EmailService(IOptions<MailServerOptions> options) : IEmailService
{
public async Task SendEmailAsync(List<string> tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default)
{
try
{
await Parallel.ForEachAsync(tos, async (to, cancellationToken) => {
await SendEmailAsync(to, from, subject, body, attachments, cancellationToken);
});
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Console.WriteLine($"Error sending email to multiple recipients: {ex.Message}");
}
}
public async Task SendEmailAsync(string to, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default)
{
try
{
using var message = new MailMessage();
message.From = new MailAddress(from);
message.To.Add(to);
message.Subject = subject;
message.Body = body;
message.IsBodyHtml = true;
message.Priority = MailPriority.Normal;
message.DeliveryNotificationOptions = DeliveryNotificationOptions.Never;
if (attachments != null)
{
foreach (var attachment in attachments)
{
if (attachment.Value is FileStream fileStream)
{
message.Attachments.Add(new Attachment(fileStream, attachment.Key));
}
if (attachment.Value is string filePath && File.Exists(filePath))
{
message.Attachments.Add(new Attachment(filePath));
}
}
}
using var cliente = new SmtpClient(options.Value.SmtpServer, options.Value.Puerto);
cliente.UseDefaultCredentials = false;
cliente.Credentials = new NetworkCredential(options.Value.Usuario, options.Value.Password);
cliente.EnableSsl = options.Value.EnableSsl;
cliente.DeliveryMethod = SmtpDeliveryMethod.Network;
await cliente.SendMailAsync(message, cancellationToken);
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Console.WriteLine($"Error sending email: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,9 @@
using DependencyInjector.Lifetimes;
namespace back.services.engine.mailing;
public interface IEmailService : IScoped
{
Task SendEmailAsync(List<string> tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default);
Task SendEmailAsync(string tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default);
}

View File

@@ -22,11 +22,7 @@ export class CryptoService {
private async fetchPublicKey(): Promise<CryptoKey> {
try {
const response = await axios.get(`${this.cryptoUrl}`, {
headers: {
'X-Thumbprint': this.thumbprintService.getThumbprint(),
},
});
const response = await axios.get(`${this.cryptoUrl}`);
if (response.status !== 200) {
throw new Error('Failed to fetch public key');
@@ -39,7 +35,7 @@ export class CryptoService {
binaryDer,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-256' },
hash: 'SHA-256',
},
false,
['encrypt']

View File

@@ -22,6 +22,28 @@ export class userService {
return this.userSubject.asObservable();
}
async systemLogin(
email: string | null | undefined,
password: string | null | undefined,
systemKey: string | null | undefined
) {
if (email == null || password == null || systemKey == null) {
return;
}
const encryptedPassword = this.cryptoService.encryptData(password);
const encryptedSystemKey = this.cryptoService.encryptData(systemKey);
const response = await axios.post('/users/login', {
email,
password: await encryptedPassword,
systemKey: await encryptedSystemKey,
});
const { jwt, refresh, usermodel } = response.data;
localStorage.setItem('jwt', jwt);
localStorage.setItem('refresh', refresh);
this.setUser(usermodel);
return usermodel;
}
async login(
email: string | null | undefined,
password: string | null | undefined
@@ -40,4 +62,25 @@ export class userService {
this.setUser(usermodel);
return usermodel;
}
async register(
name: string | null | undefined,
email: string | null | undefined,
password: string | null | undefined
): Promise<userModel> {
if (email == null || password == null) {
throw new Error('Email and password must not be null');
}
const encrypted = this.cryptoService.encryptData(password);
const response = await axios.post('/users/register', {
name,
email,
password: await encrypted,
});
const { jwt, refresh, usermodel } = response.data;
localStorage.setItem('jwt', jwt);
localStorage.setItem('refresh', refresh);
this.setUser(usermodel);
return usermodel;
}
}

View File

@@ -1 +1,29 @@
<p>forgot-password-view works!</p>
<div class="forgot-password-form-container">
<h2>Recuperar contraseña</h2>
@if(showingForm()) {
<form class="forgot-password-form" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="text-input effect-20" [class.has-content]="hasEmailContent()">
<input id="email" type="email" formControlName="email" />
<label for="email">Email</label>
<span class="focus-border">
<i></i>
</span>
</div>
<div class="form-buttons">
<svg-button
label="send-reset-link"
type="submit"
text="Enviar enlace de restablecimiento"
[disabled]="!form.valid"
></svg-button>
</div>
</form>
} @else{
<div class="success-message">
<p>
Si existe un usuario con ese correo electrónico, se enviará un enlace para
iniciar sesión y restablecer la contraseña.
</p>
</div>
}
</div>

View File

@@ -0,0 +1,18 @@
.forgot-password-form-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #fff;
}
.forgot-password-form {
display: flex;
flex-direction: column;
}
.form-buttons {
display: flex;
justify-content: flex-end;
}

View File

@@ -1,11 +1,37 @@
import { Component } from '@angular/core';
import { Component, inject, signal } from '@angular/core';
import { SvgButton } from '../../../utils/svg-button/svg-button';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { emailValidator } from '../../../utils/validators/emailValidator';
import axios from 'axios';
@Component({
selector: 'forgot-password-view',
imports: [],
imports: [SvgButton, ReactiveFormsModule],
templateUrl: './forgot-password-view.html',
styleUrl: './forgot-password-view.scss'
styleUrl: './forgot-password-view.scss',
})
export class ForgotPasswordView {
private formBuilder = inject(FormBuilder);
form = this.formBuilder.group({
email: ['', [Validators.required, emailValidator]],
});
showingForm = signal(true);
get email() {
return this.form.get('email');
}
hasEmailContent(): boolean {
const emailValue = this.email?.value;
return emailValue ? emailValue.trim().length > 0 : false;
}
async onSubmit() {
if (this.form.valid) {
this.showingForm.set(false);
await axios.post('/users/forgot-password', {
email: this.email?.value,
});
}
}
}

View File

@@ -6,7 +6,11 @@
<h2>Acceder</h2>
@if(submitError()) {
<div class="error-message">{{ submitError() }}</div>
}
@if(showRegisterLink()) {
<div class="register-link">
<a href="/register">Pincha aquí para registrarte</a>
</div>
} }
<form class="login-form" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div
id="login-view-email-input"
@@ -53,22 +57,36 @@
</span>
} }
</div>
} @if(isLoginSystem()) {
<div
id="login-view-system-key-input"
class="text-input effect-20"
[class.has-content]="hasSystemKeyContent()"
>
<input id="system-key" type="password" formControlName="systemKey" />
<label for="system-key">System Key</label>
<span class="focus-border">
<i></i>
</span>
</div>
}
<svg-button
label="login"
type="submit"
text="Entrar"
ngClass="login-button"
[disabled]="!loginForm.valid || disableLogin()"
></svg-button>
<svg-button
label="forgot-password"
type="button"
text="¿Contraseña olvidada?"
ngClass="forgot-password-button"
[disabled]="!loginForm.valid"
(click)="onForgotPassword()"
></svg-button>
<div class="login-buttons">
<svg-button
label="login"
type="submit"
text="Entrar"
ngClass="login-button"
[disabled]="!loginForm.valid || disableLogin()"
></svg-button>
<svg-button
label="forgot-password"
type="button"
text="¿Contraseña olvidada?"
ngClass="forgot-password-button"
[disabled]="!loginForm.valid"
(click)="onForgotPassword()"
></svg-button>
</div>
</form>
<div class="provider-separator">
<span class="line"></span>
@@ -108,6 +126,7 @@
text="Más opciones de inicio de sesión"
(click)="onMoreLoginOptions()"
></svg-button>
@if(!showRegisterLink()) {
<svg-button
label="register-link"
type="button"
@@ -115,6 +134,7 @@
ngClass="register-link-button"
(click)="onRegister()"
></svg-button>
}
</div>
}
</div>

View File

@@ -7,14 +7,14 @@
align-items: center;
justify-content: center;
width: 20%;
padding-top: 5rem;
padding-top: 2rem;
margin: 0 auto;
gap: 1.5rem;
.login-form {
display: flex;
flex-direction: column;
gap: 1rem;
gap: 1.5rem;
width: 100%;
}
@@ -46,6 +46,13 @@
}
}
.login-buttons {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.provider-buttons {
display: grid;
grid-template-columns: 1fr 1fr;

View File

@@ -6,7 +6,7 @@ import { Router } from '@angular/router';
import { emailValidator } from '../../../utils/validators/emailValidator';
import { PasswordValidator } from '../../../utils/validators/passwordValidator';
import { emailPasswordDistinctValidator } from '../../../utils/validators/distinctEmailPasswordValidator';
import { from, single } from 'rxjs';
import { from } from 'rxjs';
@Component({
selector: 'login-view',
@@ -19,18 +19,20 @@ export class LoginView {
private formBuilder = inject(FormBuilder);
private router = inject(Router);
private timer: any;
private cuentaAtras: number = 30;
private disableLogin = signal(false);
private entrando = signal(false);
private submitError = signal<string | null>(null);
cuentaAtras: number = 30;
disableLogin = signal(false);
entrando = signal(false);
submitError = signal<string | null>(null);
showRegisterLink = signal(false);
private loginForm = this.formBuilder.group(
loginForm = this.formBuilder.group(
{
email: ['', [Validators.required, emailValidator]],
password: [
'',
[Validators.required, Validators.minLength(8), PasswordValidator],
],
systemKey: [''],
},
{ validators: emailPasswordDistinctValidator }
);
@@ -43,6 +45,10 @@ export class LoginView {
return this.loginForm.get('password');
}
get systemKey() {
return this.loginForm.get('systemKey');
}
// Métodos para verificar si los campos tienen contenido
hasEmailContent(): boolean {
const emailValue = this.email?.value;
@@ -72,6 +78,19 @@ export class LoginView {
: false;
}
isLoginSystem(): boolean {
const emailValue = this.email?.value;
if (emailValue && emailValue == '@system') {
return true;
}
return false;
}
hasSystemKeyContent(): boolean {
const systemKeyValue = this.systemKey?.value;
return systemKeyValue ? systemKeyValue.trim().length > 0 : false;
}
ngOnDestroy() {
this.submitError.set(null);
this.limpiarTimer();
@@ -101,10 +120,20 @@ export class LoginView {
}
onSubmit() {
if (this.loginForm.valid) {
const email = this.loginForm.value.email;
const password = this.loginForm.value.password;
if (this.isLoginSystem()) {
const systemKey = this.systemKey?.value;
from(this.userService.systemLogin(email, password, systemKey)).subscribe({
next: (user) => {
this.router.navigate(['/']);
},
error: (error) => {
this.router.navigate(['/']);
},
});
} else if (this.loginForm.valid) {
this.entrando.set(true);
const email = this.loginForm.value.email;
const password = this.loginForm.value.password;
from(this.userService.login(email, password)).subscribe({
next: (user) => {
this.router.navigate(['/']);
@@ -131,9 +160,8 @@ export class LoginView {
'No se ha podido conectar con el servidor. Vuelva a intentarlo más tarde. Reconectando...'
);
} else if (error.status === 404) {
this.submitError.set(
'El usuario nunca ha existido. ¿Quieres registrarte?'
);
this.submitError.set('El usuario nunca ha existido.');
this.showRegisterLink.set(true);
}
},
});

View File

@@ -1 +1,83 @@
<p>register-view works!</p>
<div class="register-view">
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div
id="login-view-email-input"
class="text-input effect-20"
[class.has-content]="hasNameContent()"
>
<input id="name" type="text" formControlName="name" />
<label for="name">Name</label>
<span class="focus-border">
<i></i>
</span>
</div>
<div
id="login-view-email-input"
class="text-input effect-20"
[class.has-content]="hasEmailContent()"
>
<input id="email" type="email" formControlName="email" />
<label for="email">Email</label>
<span class="focus-border">
<i></i>
</span>
</div>
@if(emailHasErrors()) {
<div>
<span>* El email no es válido. </span>
@if(this.email?.hasError('email')) {
<span>El email no tiene un formato válido.</span>
}
</div>
}
<div
id="login-view-password-input"
class="text-input effect-20"
[class.has-content]="hasPasswordContent()"
>
<input id="password" type="password" formControlName="password" />
<label for="password">Password</label>
<span class="focus-border">
<i></i>
</span>
</div>
@if(passwordHasErrors()) {
<div>
<span>* La contraseña no es válida. <br /></span>
@if(this.password?.hasError('minlength')) {
<span> Debe tener al menos 8 caracteres. </span>
} @else { @if(this.password?.hasError('passwordContainsEmailOrLeet')) {
<span> No puede contener trazas del email. </span>
} @if(this.password?.hasError('invalidPassword')) {
<span>
No tiene un formato válido. <br />
Tiene que contener al menos una letra mayúscula, una letra minúscula, un
número y un carácter especial.
</span>
} }
</div>
}
<div
id="login-view-password-input"
class="text-input effect-20"
[class.has-content]="hasConfirmPasswordContent()"
>
<input
id="confirm-password"
type="password"
formControlName="confirmPassword"
/>
<label for="confirm-password">Confirm Password</label>
<span class="focus-border">
<i></i>
</span>
</div>
@if(confirmPasswordHasErrors()) {
<div>
<span>* Las dos contraseñas tienen que coincidir.</span>
</div>
}
<button type="submit">Submit</button>
</form>
</div>

View File

@@ -0,0 +1,67 @@
.register-view {
form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.text-input {
position: relative;
input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
&:focus {
border-color: #007bff;
outline: none;
}
}
label {
position: absolute;
top: 0.5rem;
left: 0.5rem;
transition: 0.2s;
}
&.has-content {
label {
top: -1rem;
left: 0.5rem;
font-size: 0.8rem;
color: #007bff;
}
}
.focus-border {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #007bff;
transform: scaleX(0);
transition: transform 0.2s;
}
&:focus-within .focus-border {
transform: scaleX(1);
}
}
button[type="submit"] {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
background: #007bff;
color: white;
cursor: pointer;
&:hover {
background: darken(#007bff, 10%);
}
}
}

View File

@@ -1,11 +1,139 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { emailValidator } from '../../../utils/validators/emailValidator';
import { SigningMethods, userModel } from '../../../models/userModel';
import { emailPasswordDistinctValidator } from '../../../utils/validators/distinctEmailPasswordValidator';
import { from } from 'rxjs';
import { Router } from '@angular/router';
import { userService } from '../../services/userService/userService';
@Component({
selector: 'register-view',
imports: [],
imports: [ReactiveFormsModule],
templateUrl: './register-view.html',
styleUrl: './register-view.scss'
styleUrl: './register-view.scss',
})
export class RegisterView {
private userService = inject(userService);
private formBuilder = inject(FormBuilder);
private router = inject(Router);
form = this.formBuilder.group(
{
name: ['', [Validators.required]],
email: ['', [Validators.required, emailValidator]],
password: ['', [Validators.required, Validators.minLength(6)]],
confirmPassword: [''],
// preferredSigninMethod: [SigningMethods.Password],
// profilePicture: [null],
// bio: ['', [Validators.maxLength(500)]],
// socialMedia: this.formBuilder.group({
// facebook: [''],
// twitter: [''],
// instagram: [''],
// }),
// termsAccepted: [false, [Validators.requiredTrue]],
},
{ validators: emailPasswordDistinctValidator }
);
get name() {
return this.form.get('name');
}
get email() {
return this.form.get('email');
}
get password() {
return this.form.get('password');
}
get confirmPassword() {
return this.form.get('confirmPassword');
}
get bio() {
return this.form.get('bio');
}
get socialMedia() {
return this.form.get('socialMedia');
}
get profilePicture() {
return this.form.get('profilePicture');
}
get preferredSigninMethod() {
return this.form.get('preferredSigninMethod');
}
// getTermsAccepted() {
// return this.form.value.termsAccepted;
// }
onSubmit() {
if (this.form.valid) {
const email = this.form.value.email;
const password = this.form.value.password;
const name = this.form.value.name;
from(this.userService.register(name, email, password)).subscribe({
next: (user) => {
this.router.navigate(['/']);
},
error: (error) => {
console.error('Register error:', error);
},
});
}
}
hasNameContent(): boolean {
const nameValue = this.name?.value;
return nameValue ? nameValue.trim().length > 0 : false;
}
hasEmailContent(): boolean {
const emailValue = this.email?.value;
return emailValue ? emailValue.trim().length > 0 : false;
}
hasPasswordContent(): boolean {
const passwordValue = this.password?.value;
return passwordValue ? passwordValue.trim().length > 0 : false;
}
hasConfirmPasswordContent(): boolean {
const confirmPasswordValue = this.confirmPassword?.value;
return confirmPasswordValue
? confirmPasswordValue.trim().length > 0
: false;
}
emailHasErrors(): boolean {
const emailControl = this.email;
return emailControl
? this.hasEmailContent() &&
emailControl.invalid &&
(emailControl.dirty || emailControl.touched)
: false;
}
passwordHasErrors(): boolean {
const passwordControl = this.password;
return passwordControl
? this.hasPasswordContent() &&
passwordControl.invalid &&
(passwordControl.dirty || passwordControl.touched)
: false;
}
confirmPasswordHasErrors(): boolean {
const confirmPasswordControl = this.confirmPassword;
return confirmPasswordControl
? this.hasConfirmPasswordContent() &&
confirmPasswordControl.invalid &&
(confirmPasswordControl.dirty || confirmPasswordControl.touched)
: false;
}
}

View File

@@ -17,6 +17,7 @@ type personModelType = {
profilePicture?: string | null;
avatar?: string | null;
socialMedia?: socialMediaType | null;
bio?: string | null;
};
export class personModel {
@@ -26,18 +27,21 @@ export class personModel {
profilePicture,
avatar,
socialMedia,
bio,
}: personModelType) {
this.id = id;
this.name = name;
this.profilePicture = profilePicture || null;
this.avatar = avatar || null;
this.socialMedia = socialMedia || null;
this.bio = bio || null;
}
public id: string;
public name: string;
public profilePicture: string | null = null;
public avatar: string | null = null;
public bio: string | null = null;
public socialMedia: {
facebook: string | null;
instagram: string | null;

View File

@@ -1,7 +1,7 @@
import { personModel } from './personModel';
import { roleModel } from './roleModel';
export enum SigningMethods {
enum SigningMethods {
Password = 'password',
MagicLink = 'magic-link',
Passkeys = 'passkeys',
@@ -11,7 +11,7 @@ export enum SigningMethods {
Microsoft = 'microsoft',
}
export class userModel extends personModel {
class userModel extends personModel {
constructor(
public override id: string,
public email: string,
@@ -66,3 +66,5 @@ export class userModel extends personModel {
true
);
}
export { SigningMethods, userModel };

Some files were not shown because too many files have changed in this diff Show More