This commit is contained in:
2025-08-28 16:54:27 +02:00
parent c7a94893a2
commit 8681056139
512 changed files with 56 additions and 38079 deletions

46
.vscode/launch.json vendored
View File

@@ -1,46 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "FRONT: DEBUG(Edge)",
"request": "launch",
"type": "msedge",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}/front/v2",
"preLaunchTask": "Start Node server with nvs latest"
},
{
"name": "(legacy) FRONT: DEBUG(Edge)",
"request": "launch",
"type": "msedge",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}/front/v1",
"preLaunchTask": "(legacy) Start Node server with nvs latest"
},
{
"name": "Attach Edge",
"type": "msedge",
"request": "attach",
"url": "http://localhost:4200/#",
"webRoot": "${workspaceFolder}"
},
{
"name": "Launch Edge (Test)",
"type": "msedge",
"request": "launch",
"url": "http://localhost:9876/debug.html",
"webRoot": "${workspaceFolder}"
},
{
"name": "Launch Edge (E2E)",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/protractor/bin/protractor",
"protocol": "inspector",
"args": ["${workspaceFolder}/protractor.conf.js"]
}
]
}

37
.vscode/tasks.json vendored
View File

@@ -1,37 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "(legacy) Start Node server with nvs latest",
"type": "shell",
"command": "nvs use latest && npm run start",
"options": {
"cwd": "${workspaceFolder}/front/v1"
},
"isBackground": true,
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
},
{
"label": "Start Node server with nvs latest",
"type": "shell",
"command": "nvs use latest && npm run start",
"options": {
"cwd": "${workspaceFolder}/front/v2"
},
"isBackground": true,
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
}

213
README.md
View File

@@ -1,213 +0,0 @@
# mmorales.photo
## Index
1. [Problem](#problem)
2. [Proposal](#proposal)
1. [Objectives](#objectives)
2. [Monetization](#monetization)
3. [Scope](#scope)
4. [Risks and Mitigations](#risks-and-mitigations)
5. [Solution](#solution)
6. [System Architecture](#system-architecture)
3. [Glossary](#glossary)
4. [Additional Context](#additional-context)
## Additional Context
- [Frontend Documentation](docs/front/frontend-documentation.md)
- [Backend Documentation](docs/back)
- [Resources](docs/resources)
## Problem
As digital photographers, there are few alternatives to showcase their work.
Many of them require web development skills.
Many lack support for selling or appointment booking.
Many do not offer enough storage.
Many do not offer custom layouts or domain naming.
Many rely on platforms that sell services as a service (SaaS).
Most rely on platforms that sell hosting as a service (IaaS).
Most do not have user interactivity, resulting in a simple gallery without feedback.
Many amateur photographers use Instagram as a gallery and portfolio. Then, they use Dropbox, WeTransfer, Google Drive, or similar platforms. They rely on manual methods for paying for services, such as wire transfer or cash, sometimes leading to loss of money or loss of a client due to client dissatisfaction.
## Proposal
A platform with three kinds of view:
1. A client view: A simple gallery with easy contact and appointment booking methods. With a history of purchases and services to access private galleries where images can be downloaded without restrictions. These galleries must be able to be shared across users so one pays, all get access.
2. A content manager view: A simple administration page for uploading content, customizing the current content (such as website name, icon, and current images displayed), and creating buyers' galleries. It must also be able to moderate user-generated content, such as public gallery comments.
3. An administrator view: A more complex display of web-related content. The admin can edit service-related items, such as adding content managers, editing payment methods, changing website name, icon, fonts, and component CSS. It must also be able to look up non-sensitive user details such as name or email to assist users in need.
The solutions must be oriented towards user experience and satisfaction.
It must also provide user security and protect photographers' work.
For non-techies, it must provide an easy way to manage, use, and host; the out-of-the-box experience must be excellent.
For techies, it must provide a self-hosted environment, allowing customization of less critical parts such as mailing, frontend layout, and database provider.
### Objectives
We expect:
- To fulfill the needs of new professionals and provide a solid alternative for established ones.
- A secure and professional way to deliver digital photography work to clients.
- A collaborative platform between photographers and clients to enable better work.
- The easiest-to-use and best out-of-the-box experience for new clients.
### Monetization
__What do we sell?__ We sell a fire-and-forget service.
__What do customers pay for?__ Non-techies pay for setup, management, support, and storage. Techies pay for support.
__How do customers receive the product?__ Non-techies receive a pre-made setup on their desired hosting. Techies receive a click-and-run service.
__How much do we receive per product?__ As a product, we do not receive anything. As a service, we license the service monthly, yearly, or by one-hour support service.
### Scope
#### What IS included
- Provide a friendly, comfortable, and fast frontend; an agile and resilient backend.
- Offer broad customization options regarding infrastructure.
- Deliver the most simple and satisfying out-of-the-box experience possible.
- Enable integration with multiple cloud storage providers and local storage.
- Support for multi-language and accessibility features.
- Ensure data privacy and security for users and photographers.
- Provide basic analytics and usage statistics for administrators.
- Allow easy deployment on various hosting environments (cloud, on-premises, hybrid).
#### What is NOT included
- Payment gateways.
- Extensive customization of the visual design.
- Hosting management tasks.
- Advanced marketing automation tools.
- Third-party plugin marketplace.
- Deep integration with external CRM or ERP systems.
### Risks and Mitigations
| Risk | Description | Mitigation |
|------|-------------|------------|
| Vendor lock-in | Dependence on a single cloud provider may limit flexibility and increase costs. | Support multiple storage providers and allow easy migration between them. |
| Data loss or corruption | Images or user data could be lost due to hardware failure or software bugs. | Implement regular backups, redundancy, and data integrity checks. |
| Security breaches | Unauthorized access to private galleries or sensitive user data. | Use strong encryption (HTTPS, AES), MFA, and regular security audits. |
| Scalability issues | Performance degradation as the number of users or images grows. | Design for horizontal scalability and monitor system performance. |
| Legal and compliance | Failure to comply with data protection laws (GDPR, etc.). | Store data in compliant regions and provide clear privacy policies. |
| User adoption | Users may find the platform difficult to use or not see its value. | Focus on user experience, provide onboarding guides, and collect feedback. |
| Third-party service outages | External services (cloud, identity providers) may become unavailable. | Implement fallback mechanisms and monitor service health. |
| Cost overruns | Unexpected expenses in infrastructure or development. | Monitor costs, set budgets, and optimize resource usage. |
### Solution
Based on a three-layered architecture, each layer must present a plug-and-play architecture.
__As frontend__, we are starting with Angular as a base, then creating Vue and React alternatives.
It will follow a clean architecture based on the scream architecture approach.
To make rendering easy for low-end/home servers, it will mainly use client-side rendering.
For identity verification, the frontend will rely on OpenID data provided by the backend, using a cookie ID and JWE for identification.
For UI layout, mobile and tablet will be preferred as end devices. Computers and large-sized devices will be last in layout responsibility. The UI will be built by composing different small components, following a Vue-like philosophy.
__As backend__, we are deploying a C# .NET engine. As a self-host-aware project, we need to ensure platform compatibility. A cloud-native approach will be taken, providing a system-agnostic platform. Cross-compiling will be a must. Database-agnostic, it will use SQLite as the default database for fallback and non-customized database. PostgreSQL will be used for SQL-based database development, making it pluggable and interchangeable with Microsoft SQL Server and other SQL providers like CockroachDB.
For architecture, we will take a Domain-Driven approach. Using EntityFramework for database operations makes development faster and database-agnostic.
For security, all communications will be made via HTTPS and encrypted with military-grade AES.
Also, to make user registration easier, we will use as many identity providers as possible, such as Google, Facebook, Instagram... Once registered, users need to access as easily as possible, so passwordless access should be implemented. For security, an MFA system will be required.
To be able to respond to energy shortages, we will use DAPR and some edge computing technologies.
To be more resilient against some attacks, we will implement BFF (Backend for Frontend) technology.
For custom gallery creation, we will use a parallel processor based on messaging.
__As database__, we are going to choose a database-agnostic approach.
Since during development we will ignore database layout, we are going to expose two configurations for two kinds of data levels.
User-related data will be presented as the SensibleData database.
Images and other blobs will be presented as the BlobData database.
As a cloud-native approach, we will take blob storage as a service-independent module, so users can use their own disk space or services such as:
- __Amazon S3:__ Highly scalable, reliable, and widely used object storage service.
- __Microsoft Azure Blob Storage:__ Secure and scalable storage for unstructured data, integrated with the Azure ecosystem.
- __Google Cloud Storage:__ Global object storage with strong integration to Google Cloud services.
- __DigitalOcean Spaces:__ Simple and cost-effective object storage compatible with the S3 API.
- __Backblaze B2:__ Affordable cloud storage with S3 compatibility.
- __Wasabi:__ High-performance, low-cost cloud storage with S3 API support.
- __IBM Cloud Object Storage:__ Enterprise-grade, scalable object storage.
- __MinIO:__ Self-hosted, S3-compatible object storage solution for on-premises or private cloud.
### System Architecture
![Diagram of the connections between the described components. Frontend connected to BFF, BFF connected to BlobProvider, BFF connected to Backend, Backend connected to DataProvider and Backend connected to Identity provider.](docs/resources/root/architecturalComponentLayout.svg)
At system level, since identity and data providers are not controlled parts, there are three main components:
- Frontend
- BFF
- Backend
Frontend will speak ONLY with BFF via REST commands. Only sensible data will be encrypted via AES.
Frontend will GET lazily all the images for galleries.
BFF will redirect GET image requests to configured CDN or will serve them if no CDN is configured.
BFF will cache repeated and critical requests.
BFF will redirect to Backend in case of user creation, login or update; selling activity as booking or buying; image creation, edition or delete.
Backend will contact Identity Providers such as Google or Instagram at login time.
Backend will generate and validate login tokens.
Backend will create, retrieve, edit and delete user and image data.
Since frontend will be device dependent in the future, BFF will give flexibility for identifying client users.
---
## Glossary
__Frontend:__ The part of the application that users interact with directly, typically the website or app interface.
__Backend:__ The server-side part of the application that handles business logic, data storage, and communication with other services.
__SaaS (Software as a Service):__ A software distribution model in which applications are hosted by a service provider and made available to customers over the internet.
__IaaS (Infrastructure as a Service):__ A form of cloud computing that provides virtualized computing resources over the internet.
__Client-side rendering:__ Rendering of web pages in the user's browser using JavaScript frameworks.
__Server-side rendering (SSR):__ Rendering of web pages on the server before sending them to the user's browser.
__OpenID:__ An authentication protocol that allows users to log in to multiple services with a single identity.
__JWE (JSON Web Encryption):__ A standard for encrypting data in JSON format, often used for secure transmission of authentication tokens.
__MFA (Multi-Factor Authentication):__ A security system that requires more than one method of authentication from independent categories of credentials.
__BFF (Backend for Frontend):__ An architectural pattern where a dedicated backend is built for each frontend application to optimize communication and security.
__Domain-Driven Design (DDD):__ An approach to software development that focuses on modeling software to match a domain's business concepts and logic.
__EntityFramework:__ An object-relational mapper (ORM) for .NET, used to interact with databases using .NET objects.
__Blob Storage:__ A service for storing large amounts of unstructured data, such as images, videos, and documents.
__Plug-and-play architecture:__ A design approach that allows components to be easily added, removed, or replaced without affecting the rest of the system.
__CDN (Content Delivery Network):__ A network of servers distributed geographically to deliver content more efficiently to users based on their location.
__AES (Advanced Encryption Standard):__ A symmetric encryption algorithm widely used for securing sensitive data.
__REST (Representational State Transfer):__ An architectural style for designing networked applications, often used for APIs.
__DAPR (Distributed Application Runtime):__ A runtime that simplifies building distributed systems by providing APIs for common tasks like state management and pub/sub messaging.
__Edge Computing:__ A distributed computing paradigm that brings computation and data storage closer to the location where it is needed to improve response times and save bandwidth.
__Parallel Processing:__ A method of processing data in which multiple processors execute tasks simultaneously to increase efficiency and performance.
__API (Application Programming Interface):__ Un conjunto de definiciones y protocolos para construir e integrar software de aplicaciones.
__OAuth:__ Un estándar abierto para la autorización que permite a los usuarios compartir recursos entre aplicaciones sin compartir credenciales.
__CI/CD (Continuous Integration/Continuous Deployment):__ Un conjunto de prácticas que automatizan el desarrollo, las pruebas y la implementación de software.
__Microservicios:__ Un estilo arquitectónico que estructura una aplicación como un conjunto de servicios pequeños y autónomos.
__Load Balancer:__ Un dispositivo o software que distribuye tráfico de red o aplicación entre varios servidores para mejorar la eficiencia y la disponibilidad.
__Cache:__ Un almacenamiento temporal de datos para acelerar el acceso a información frecuentemente utilizada.
__JWT (JSON Web Token):__ Un estándar para transmitir información de forma segura entre partes como un objeto JSON.

View File

@@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.8",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

View File

@@ -1,5 +0,0 @@
{
"email": "sys@t.em",
"key": "b60e166e-d4a5-416e-a7c9-142d05fb7f31",
"password": "8C3,uTÑ<hñ61qQs3"
}

View File

@@ -1,14 +0,0 @@
namespace back.DTO;
public class PhotoFormModel
{
public required string UserId { get; set; }
public required string Title { get; set; }
public string? Description { get; set; }
public string[]? Tags { get; set; }
public string[]? People { get; set; }
public IFormFile? Image { get; set; }
public string? Ubicacion { get; set; }
public string? Evento { get; set; }
public bool IsPublic { get; set; }
}

View File

@@ -1,9 +0,0 @@
using back.DataModels;
namespace back.DTO;
public class UserDto
{
public string Id { get; set; } = null!;
public ICollection<RoleDto> Roles { get; set; } = [];
}

View File

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

View File

@@ -1,44 +0,0 @@
using MCVIngenieros.Transactional.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
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,44 +0,0 @@
using MCVIngenieros.Transactional.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
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,72 +0,0 @@
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
public record PermissionDto
{
public string Id { get; set; } = null!;
}
[Table("Permissions")]
public partial class Permission : IEntity<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();
}
public bool IsNull => this is null;
public object Clone() => (Permission)MemberwiseClone();
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not Permission other) throw new ArgumentException("Object is not a Person");
return CompareTo(other);
}
public int CompareTo(Permission? other)
{
if (other is null) return 1;
if (ReferenceEquals(this, other)) return 0;
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
}
public PermissionDto ToDto()
{
return new PermissionDto
{
Id = Id
};
}
// 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,70 +0,0 @@
using MCVIngenieros.Transactional.Abstractions;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Persons")]
public partial class Person: IEntity<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 bool IsNull => this is null;
public object Clone() => (Person)MemberwiseClone();
public int CompareTo(object? obj)
{
if(obj is null) return 1;
if (obj is not Person other) throw new ArgumentException("Object is not a Person");
return CompareTo(other);
}
public int CompareTo(Person? other)
{
if (other is null) return 1;
if (ReferenceEquals(this, other)) return 0;
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
}
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,66 +0,0 @@
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Photos")]
public partial class Photo : IEntity<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();
}
public bool IsNull => this is null;
public object Clone() => (Photo)MemberwiseClone();
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not Photo other) throw new ArgumentException("Object is not a Person");
return CompareTo(other);
}
public int CompareTo(Photo? other)
{
if (other is null) return 1;
if (ReferenceEquals(this, other)) return 0;
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -1,119 +0,0 @@
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,127 +0,0 @@
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
public class RoleDto
{
public string Id { get; set; } = null!;
public List<PermissionDto> Permissions { get; set; } = [];
}
[Table("Roles")]
public partial class Role : IEntity<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 bool IsNull => this is null;
public object Clone() => (Role)MemberwiseClone();
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not Role other) throw new ArgumentException("Object is not a Person");
return CompareTo(other);
}
public int CompareTo(Role? other)
{
if (other is null) return 1;
if (ReferenceEquals(this, other)) return 0;
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
}
public RoleDto ToDto()
{
return new RoleDto
{
Id = Id,
Permissions = [.. Permissions.Select(p => p.ToDto())]
};
}
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,33 +0,0 @@
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

@@ -1,15 +0,0 @@
namespace back.DataModels;
public class SystemKey
{
public string Email { get; set; } = User.SystemUser.Email;
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);
}
}

View File

@@ -1,32 +0,0 @@
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,86 +0,0 @@
using back.DTO;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace back.DataModels;
[Table("Users")]
public class User : IEntity<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 UserDto ToDto() => new()
{
Id = Id,
Roles = [.. Roles.Select(r => r.ToDto())]
};
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 bool IsNull => this is null;
public object Clone() => (User)MemberwiseClone();
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not User other) throw new ArgumentException("Object is not a Person");
return CompareTo(other);
}
public int CompareTo(User? other)
{
if (other is null) return 1;
if (ReferenceEquals(this, other)) return 0;
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
}
public const string SystemUserId = "00000000-0000-0000-0000-000000000001";
public static readonly User SystemUser = new(
id: SystemUserId,
email: "sys@t.em",
password: "",
createdAt: DateTime.UtcNow
)
{
Roles = [Role.AdminRole, Role.ContentManagerRole, Role.UserRole]
};
}

View File

@@ -1,14 +0,0 @@
namespace back.Options;
public sealed class DatabaseConfig
{
public const string BlobStorage = "Databases:Blob";
public const string DataStorage = "Databases:Data";
public required string Provider { get; set; }
public string? DatabaseName { get; set; }
public string? AccountKey { get; set; }
public string? TokenCredential { get; set; }
public string? BaseUrl { get; set; }
public string? ConnectionString { get; set; }
public string? SystemContainer { get; set; }
}

View File

@@ -1,6 +0,0 @@
namespace back.Options;
public sealed class Databases
{
public string? BaseDirectory { get; set; }
}

View File

@@ -1,10 +0,0 @@
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

@@ -1,48 +0,0 @@
using back.ServicesExtensions;
using MCVIngenieros.Healthchecks;
namespace back;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.UseExtensions();
builder.Services.AddControllers();
builder.Services.AddHealthChecksSupport().DiscoverHealthChecks();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseCors("AllowAll");
app.MapControllers();
app.Run();
}
}

View File

@@ -1,24 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5250",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7273;http://localhost:5250",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,49 +0,0 @@
using back.Options;
using back.persistance.data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace back.ServicesExtensions;
public static partial class ServicesExtensions
{
private static IServiceCollection AddDatabaseContext(this IServiceCollection services)
{
services.AddContext<DataContext>();
return services;
}
private static IServiceCollection AddContext<T>(this IServiceCollection services)
where T : DbContext
{
var config = services
.BuildServiceProvider()
.GetRequiredService<IOptionsSnapshot<DatabaseConfig>>()
.Get(DatabaseConfig.DataStorage);
services.AddDbContext<T>(options =>
{
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,92 +0,0 @@
using back.Options;
using Microsoft.EntityFrameworkCore;
using System.Text.RegularExpressions;
namespace back.ServicesExtensions;
public enum DatabaseProvider
{
/* -- Relational databases supported by EF Core -- */
SUPPORTED, // Placeholder for supported databases.
InMemory,
Sqlite,
PostgreSQL,
CockroachDB, // CockroachDB is compatible with PostgreSQL.
SQLServer,
MariaDB,
MySQL,
Oracle, // Oracle is supported by EF Core but requires a separate package.
/* -- NoSQL are not supported by EF -- */
NOT_SUPPORTED, // Placeholder for unsupported databases.
Firebird, // Firebird is supported by EF Core but requires a separate package.
Db2, // Db2 is supported by EF Core but requires a separate package.
SAPHana, // SAP HANA is supported by EF Core but requires a separate package.
Sybase, // Sybase is supported by EF Core but requires a separate package.
Cosmos, // Cosmos DB is database supported by EF Core.
MongoDB,
InfluxDB,
Redis,
Cassandra,
ElasticSearch,
CouchDB,
RavenDB,
Neo4j,
OrientDB,
ArangoDB,
ClickHouse,
Druid,
TimescaleDB,
}
public static partial class DbContextOptionsBuilderExtensions
{
private static string SupportedDbs()
=> string.Join(", ", Enum.GetValues<DatabaseProvider>()
.Where(db => db > DatabaseProvider.SUPPORTED && db < DatabaseProvider.NOT_SUPPORTED)
.OrderBy(db => db)
.Select(db => db.ToString()));
public static void UseDatabaseConfig(this DbContextOptionsBuilder options, DatabaseConfig config)
{
var providerName = Enum.GetNames<DatabaseProvider>()
.FirstOrDefault(name => name.Equals(config.Provider, StringComparison.InvariantCultureIgnoreCase));
if (!Enum.TryParse(providerName, out DatabaseProvider provider))
{
throw new InvalidOperationException($"Unsupported database provider: {config.Provider} -- Supported providers are: {SupportedDbs()}");
}
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:
options.UseInMemoryDatabase(config.ConnectionString);
break;
case DatabaseProvider.PostgreSQL or DatabaseProvider.CockroachDB:
options.UseNpgsql(config.ConnectionString);
break;
case DatabaseProvider.SQLServer:
options.UseSqlServer(config.ConnectionString);
break;
case DatabaseProvider.MySQL or DatabaseProvider.MariaDB:
options.UseMySql(config.ConnectionString, ServerVersion.AutoDetect(config.ConnectionString));
break;
default:
throw new InvalidOperationException($"Unsupported database provider: {config.Provider}");
}
}
[GeneratedRegex(@"Data Source=([^;]+)")]
private static partial Regex SQLiteRegex();
}

View File

@@ -1,76 +0,0 @@
using back.healthchecks.Options;
using back.Options;
namespace back.ServicesExtensions;
public static partial class ServicesExtensions
{
private static IConfiguration ConfigureOptions(this IServiceCollection services)
{
IConfiguration config = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
string? baseDirectory = null;
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.Configure<HealthChecksConfigs>(HealthChecksConfigs.Sqlite, config.GetSection(HealthChecksConfigs.Sqlite));
services.PostConfigure<Databases>(databases =>
{
if (!string.IsNullOrEmpty(databases.BaseDirectory) && !Directory.Exists(databases.BaseDirectory))
{
try
{
Directory.CreateDirectory(databases.BaseDirectory);
Console.WriteLine($"Base directory created at: {databases.BaseDirectory}");
baseDirectory = databases.BaseDirectory;
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to create base directory at {databases.BaseDirectory}. " +
"Please ensure the path is valid and accessible.", ex
);
}
}
});
services.PostConfigure<DatabaseConfig>(DatabaseConfig.DataStorage, config =>
{
PostConfigureDatabaseConfig(config, baseDirectory);
});
services.PostConfigure<DatabaseConfig>(DatabaseConfig.BlobStorage, config =>
{
PostConfigureDatabaseConfig(config, baseDirectory);
});
return config;
}
private static void PostConfigureDatabaseConfig(DatabaseConfig config, string? baseDirectory)
{
if (!string.IsNullOrEmpty(config.SystemContainer))
{
var path = config.SystemContainer;
if (!string.IsNullOrEmpty(baseDirectory))
{
path = Path.Combine(baseDirectory, path);
}
try
{
Directory.CreateDirectory(path);
Console.WriteLine($"System container for {config.Provider} created at: {path}");
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to create system container at {path}. " +
"Please ensure the path is valid and accessible.", ex
);
}
}
}
}

View File

@@ -1,49 +0,0 @@
using back.persistance.data;
using System.Text.Json.Serialization;
using back.services.engine.SystemUser;
using DependencyInjector;
using System.Text.Json;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
using MCVIngenieros.Transactional.Implementations.EntityFramework;
namespace back.ServicesExtensions;
public static partial class ServicesExtensions
{
public static IServiceCollection UseExtensions(this IServiceCollection services)
{
//var config =
services.ConfigureOptions();
services.AddMemoryCache();
services.AddDatabaseContext();
services.AddServices();
services.AddScoped<ITransactionalService<DataContext>, EntityFrameworkTransactionalService<DataContext>>();
services.AddSingleton(new JsonSerializerOptions {
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Converters = {
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
},
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,
ReadCommentHandling = JsonCommentHandling.Skip,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement,
});
using var scope = services.BuildServiceProvider().CreateScope();
scope.ServiceProvider
.GetRequiredService<ISystemUserGenerator>().GenerateAsync().Wait();
return services;
}
}

View File

@@ -1,34 +0,0 @@
{
"Databases": {
"BaseDirectory": ".program_data",
"Data": {
"Provider": "sqlite",
"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
},
"HealthChecksConfigs": {
"CacheDuration": "00:30:00",
"Timeout": "00:00:05",
"AssembliesToScan": [
"back"
],
"Sqlite": {
"RetryAttempts": 2,
"Timeout": "00:05:00",
"RetryDelay": "00:00:10",
"Severity": "Info"
}
}
}

View File

@@ -1,18 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Databases": {
"Data": {
"Provider": "sqlite",
"ConnectionString": "Data Source=data/app.db;Cache=Shared"
},
"Blob": {
"Provider": "system",
"baseUrl": "https://back.mmorales.photo/api/photo/{id}/{res}"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,54 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.15.0" />
<PackageReference Include="MailKit" Version="4.13.0" />
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
<PackageReference Include="Mapster.EFCore" Version="5.1.1" />
<PackageReference Include="MCVIngenieros.Healthchecks" Version="0.0.1" />
<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" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.UnitOfWork" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="Oracle.EntityFrameworkCore" Version="9.23.90" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
<PackageReference Include="System.Diagnostics.EventLog" Version="9.0.8" />
<PackageReference Include="System.Text.Json" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\nuget\DependencyInjector\DependencyInjector.csproj" />
<ProjectReference Include="..\..\nuget\Transactional\MCVIngenieros.Transactional.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,43 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
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}") = "MCVIngenieros.Transactional", "..\..\nuget\Transactional\MCVIngenieros.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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presentation", "..\backend\Presentation\Presentation.csproj", "{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F531A9C8-70D1-45AA-B4AA-AC49FCADAE3D}
EndGlobalSection
EndGlobal

View File

@@ -1,44 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace back.controllers;
public class ValidationErrors
{
public string? Field { get; set; }
public string? Message { get; set; }
}
public class ExecutionErrors
{
public Exception? Exception { get; set; }
public string? Message { get; set; }
}
public abstract class ResponseBase
{
public object? Data { get; set; }
public string? Message { get; set; }
public bool Success { get; set; }
public int StatusCode { get; set; }
public ValidationErrors[] ValidationErrors { get; set; }
public ExecutionErrors[] ExecutionErrors { get; set; }
}
public record LoginRequest(string Username, string Password);
[ApiController, Route("api/[controller]")]
public class AuthController(IAuthService authService) : ControllerBase
{
private readonly IAuthService _authService = authService;
[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
{
// validar que el usuario y la contraseña sean correctos
// obtener el token JWT encriptado
// obtener el refresh token
// devolver el token JWT y el refresh token en los headers de las respuestas
// devolver datos del usuario en el body de la respuesta
}
}

View File

@@ -1,21 +0,0 @@
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,67 +0,0 @@
using back.DataModels;
using back.DTO;
using back.services.bussines.PhotoService;
using Microsoft.AspNetCore.Mvc;
namespace back.controllers;
[Route("api/[controller]")]
[ApiController]
public class PhotosController(IPhotoService photoService) : ControllerBase
{
private readonly IPhotoService _photoService = photoService;
// GET: api/<PhotoController>
[HttpGet]
public async Task<ActionResult<IEnumerable<Photo>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
{
(int totalItems, IEnumerable<Photo>? pageData) = await _photoService.GetPage(page, pageSize);
Response.Headers.Append("X-Total-Count", totalItems.ToString());
return Ok(pageData);
}
// GET api/<PhotoController>/5
[HttpGet("{id}/{res}")]
public async Task<IActionResult> Get(string id, string res)
{
(string? mediaType, byte[]? fileBytes) = await _photoService.GetBytes(id, res.ToLower());
if(fileBytes == null)
return NotFound();
return File(fileBytes, mediaType ?? "image/jpeg");
}
// POST api/<PhotoController>
[HttpPost]
public async Task<IActionResult> Post([FromForm] PhotoFormModel form)
{
try
{
if (form.Image == null || form.Image.Length == 0)
return BadRequest("No image uploaded.");
await _photoService.Create(form);
return Created();
}
catch
{
return BadRequest();
}
}
//// PUT api/<PhotoController>
[HttpPut]
public async Task<IActionResult> Put([FromBody] Photo photo)
{
await _photoService.Update(photo);
return NoContent();
}
// DELETE api/<PhotoController>/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(string id)
{
await _photoService.Delete(id);
return NoContent();
}
}

View File

@@ -1,90 +0,0 @@
using back.DataModels;
using back.services.bussines;
using back.services.bussines.UserService;
using Microsoft.AspNetCore.Mvc;
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(IUserService user) : ControllerBase
{
private readonly IUserService _user = user;
// GET: api/<UsersController>
//[HttpGet]
//public async Task<ActionResult<IEnumerable<UserModel>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
//{
// var users = await _userContext.GetPage(page, pageSize);
// var totalItems = await _userContext.GetTotalItems();
// Response.Headers.Append("X-Total-Count", totalItems.ToString());
// return Ok(users);
//}
//// GET api/<UsersController>/5
//[HttpGet("{id}")]
//public async Task<IActionResult> Get(Guid id)
//{
// var user = await _userContext.GetById(id);
// if (user == null)
// return NotFound();
// return Ok(user);
//}
[HttpPost("[action]")]
public async Task<IActionResult> Login(
[FromHeader(Name = "X-client-thumbprint")] string clientId,
[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(Errors.BadRequest.Description);
if (user.Email.Equals(DataModels.User.SystemUser.Email, 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.ToDto());
}
var existingUser = await _user.Login(user.Email, user.Password, clientId);
if (existingUser == null)
return Unauthorized(Errors.Unauthorized.Description);
return Ok(existingUser);
}
[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

@@ -1,6 +0,0 @@
namespace back.healthchecks.Options;
public partial class HealthChecksConfigs : MCVIngenieros.Healthchecks.Options.HealthChecksConfigs
{
public const string Sqlite = "Sqlite";
}

View File

@@ -1,52 +0,0 @@
using back.healthchecks.Options;
using back.Options;
using MCVIngenieros.Healthchecks;
using MCVIngenieros.Healthchecks.Abstracts;
using Microsoft.Extensions.Options;
namespace back.healthchecks;
public class SqliteHealthCheck(IOptionsMonitor<DatabaseConfig> databaseConfig, IOptionsMonitor<HealthChecksConfigs> healthchecksConfig) : HealthCheck
{
private readonly DatabaseConfig databaseConfig = databaseConfig.Get(DatabaseConfig.DataStorage);
private readonly HealthChecksConfigs hcConfig = healthchecksConfig.Get(HealthChecksConfigs.Sqlite);
public string Description => "Conecta con la base de datos SQLite y trata de hacer una consulta sobre la tabla Users.";
public int? RetryAttempts => hcConfig.RetryAttempts ?? 2;
public TimeSpan? Timeout => hcConfig.Timeout ?? TimeSpan.FromSeconds(5);
public TimeSpan? RetryDelay => hcConfig.RetryDelay ?? TimeSpan.FromSeconds(1);
public HealthCheckSeverity? Severity => hcConfig.Severity ?? HealthCheckSeverity.Critical;
public override Task<HealthCheckResult> CheckAsync(CancellationToken cancellationToken = default)
{
var isHealthy = false;
var details = string.Empty;
try
{
using var connection = new Microsoft.Data.Sqlite.SqliteConnection(databaseConfig.ConnectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = $"SELECT COUNT(1) FROM Users WHERE Id = '{DataModels.User.SystemUserId}';";
var result = command.ExecuteScalar();
if (result != null && Convert.ToInt32(result) == 1)
{
isHealthy = true;
details = "Connection to SQLite database successful and SystemUser exists.";
}
else
{
details = "Connection to SQLite database successful but SystemUser does not exist.";
}
}
catch (Exception ex)
{
details = $"Failed to connect to SQLite database: {ex.Message}";
}
return Task.FromResult(new HealthCheckResult(isHealthy)
{
Details = details,
Severity = isHealthy ? HealthCheckSeverity.Info : HealthCheckSeverity.Critical
});
}
}

View File

@@ -1,111 +0,0 @@
using back.Options;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace back.persistance.blob;
public class FileSystemImageStorageService(
IOptions<Databases> systemOptions,
IOptionsMonitor<DatabaseConfig> options,
IMemoryCache memoryCache
) : IBlobStorageService
{
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 path = Path.Join(RootPath, config.SystemContainer, fileName);
var directory = Path.GetDirectoryName(path);
if (directory != null && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
return path;
}
public async Task Delete(string fileName)
{
try
{
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)
{
throw new InvalidOperationException($"Error deleting file {fileName}: {ex.Message}", ex);
}
}
public async Task<Stream?> GetStream(string fileName)
{
var path = GetFullPath(fileName);
if (File.Exists(path))
{
if (cache.TryGetValue(path, out 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);
cache.CreateEntry(path)
.SetValue(fileStream)
.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // Cache for 30 minutes
return fileStream;
}
return null;
}
public async Task<byte[]?> GetBytes(string fileName)
{
var stream = await GetStream(fileName);
if (stream != null)
{
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
return null;
}
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, options: new FileStreamOptions {
Access = FileAccess.Write,
BufferSize = 4096,
Mode = FileMode.OpenOrCreate,
Share = FileShare.Read,
});
blobStream.Seek(0, SeekOrigin.Begin);
await blobStream.CopyToAsync(fileStream);
blobStream.Seek(0, SeekOrigin.Begin);
}
public async Task Update(Stream blobStream, string fileName)
{
var path = GetFullPath(fileName);
if (File.Exists(path))
{
await Delete(fileName);
}
await Save(blobStream, fileName);
}
}

View File

@@ -1,13 +0,0 @@
using DependencyInjector.Abstractions.Lifetimes;
namespace back.persistance.blob;
public interface IBlobStorageService : ISingleton
{
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

@@ -1,56 +0,0 @@
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);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -1,752 +0,0 @@
// <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

@@ -1,583 +0,0 @@
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

@@ -1,749 +0,0 @@
// <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

@@ -1,200 +0,0 @@
-- 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

@@ -1,28 +0,0 @@
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

@@ -1,68 +0,0 @@
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

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

View File

@@ -1,68 +0,0 @@
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

@@ -1,307 +0,0 @@
//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

@@ -1,15 +0,0 @@
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

@@ -1,25 +0,0 @@
//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

@@ -1,30 +0,0 @@
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

@@ -1,40 +0,0 @@
//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

@@ -1,15 +0,0 @@
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

@@ -1,32 +0,0 @@
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

@@ -1,10 +0,0 @@
using back.DataModels;
using DependencyInjector.Abstractions.Lifetimes;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IPermissionRepository : IRepository<Permission>, IScoped
{
Task SeedDefaultPermissions();
}

View File

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

View File

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

View File

@@ -1,10 +0,0 @@
using back.DataModels;
using DependencyInjector.Abstractions.Lifetimes;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IRoleRepository : IRepository<Role>, IScoped
{
Task SeedDefaultRoles();
}

View File

@@ -1,14 +0,0 @@
using back.DataModels;
using DependencyInjector.Abstractions.Lifetimes;
using MCVIngenieros.Transactional.Abstractions.Interfaces;
namespace back.persistance.data.repositories.Abstracts;
public interface IUserRepository : IRepository<User>, 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

@@ -1,34 +0,0 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using MCVIngenieros.Transactional.Implementations.EntityFramework;
namespace back.persistance.data.repositories;
public class PermissionRepository(DataContext context) : ReadWriteRepository<Permission>(context), IPermissionRepository
{
// Implement methods specific to Photo repository if needed
public async Task SeedDefaultPermissions()
{
var defaultPermissions = new List<Permission>
{
Permission.ViewContentPermission,
Permission.LikeContentPermission,
Permission.EditContentPermission,
Permission.DeleteContentPermission,
Permission.CreateContentPermission,
Permission.EditUserPermission,
Permission.DeleteUserPermission,
Permission.DisableUserPermission,
Permission.CreateUserPermission,
Permission.EditWebConfigPermission
};
foreach (var permission in defaultPermissions)
{
if (!Entities.Any(p => p.Id == permission.Id))
{
Entities.Add(permission);
}
}
await SaveChanges();
}
}

View File

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

View File

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

View File

@@ -1,27 +0,0 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using MCVIngenieros.Transactional.Implementations.EntityFramework;
namespace back.persistance.data.repositories;
public class RoleRepository(DataContext context) : ReadWriteRepository<Role>(context), IRoleRepository
{
// Implement methods specific to Photo repository if needed
public async Task SeedDefaultRoles()
{
var defaultRoles = new List<Role>
{
Role.AdminRole,
Role.UserRole,
Role.ContentManagerRole
};
foreach (var role in defaultRoles)
{
if (!Entities.Any(p => p.Id == role.Id))
{
Entities.Add(role);
}
}
await SaveChanges();
}
}

View File

@@ -1,75 +0,0 @@
using back.DataModels;
using back.persistance.data.repositories.Abstracts;
using MCVIngenieros.Transactional.Implementations.EntityFramework;
using Microsoft.EntityFrameworkCore;
namespace back.persistance.data.repositories;
public class UserRepository(
DataContext context
) : ReadWriteRepository<User>(context), IUserRepository
{
public async Task<User?> GetByEmail(string email)
{
try
{
if (string.IsNullOrEmpty(email)) return null;
return await Entities.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 Entities.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 Entities
.Include(u => u.Roles)
.ThenInclude(r => r.Permissions)
.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 Entities.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

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

View File

@@ -1,23 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,14 +0,0 @@
//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,11 +0,0 @@
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

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

View File

@@ -1,15 +0,0 @@
using back.DataModels;
using back.DTO;
using DependencyInjector.Abstractions.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

@@ -1,82 +0,0 @@
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

@@ -1,13 +0,0 @@
using back.DataModels;
using DependencyInjector.Abstractions.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

@@ -1,142 +0,0 @@
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;
using System.Text.Json;
namespace back.services.bussines.UserService;
public class UserService(
IUserRepository userRepository, ICryptoService cryptoService,
IEmailService emailService,
IBlobStorageService blobStorageService,
JsonSerializerOptions jsonSerializerOptions
) : 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 decryptedPass)
{
var salt = await _repository.GetUserSaltByEmail(email);
var hashedPassword = _cryptoService.HashPassword(decryptedPass, salt);
var user = await _repository.Login(email, hashedPassword ?? string.Empty);
return user;
}
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 user = await Login(email, decryptedPass ?? 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)
{
var decryptedPassword = _cryptoService.Decrypt(clientId, password) ?? string.Empty;
var decryptedsystemKey = _cryptoService.Decrypt(clientId, systemKey) ?? string.Empty;
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(decryptedPassword) || string.IsNullOrEmpty(decryptedsystemKey))
{
return null;
}
if (!email.Equals(User.SystemUser.Email, StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
var systemKeyBytes = await _blobStorageService.GetBytes("systemkey.lock");
var systemKeyString = Encoding.UTF8.GetString(systemKeyBytes ?? []);
var systemKeyObject = JsonSerializer.Deserialize<SystemKey>(systemKeyString, jsonSerializerOptions);
if (systemKeyObject == null || !systemKeyObject.IsValid(email, decryptedPassword, decryptedsystemKey))
{
return null;
}
if (!await _repository.ExistsByEmail(email))
{
return null;
}
var user = await _repository.GetByEmail(email);
if (user == null)
{
return null;
}
var loggedUser = await Login(user.Email!, decryptedPassword);
return loggedUser;
}
}

View File

@@ -1,175 +0,0 @@
using Microsoft.Extensions.Caching.Memory;
using System.Security.Cryptography;
namespace back.services.engine.Crypto;
public class CryptoService(IMemoryCache cache) : ICryptoService
{
private readonly IMemoryCache _cache = cache;
private readonly MemoryCacheEntryOptions _CacheOptions = new()
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1),
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
SlidingExpiration = TimeSpan.FromMinutes(30),
Priority = CacheItemPriority.High,
PostEvictionCallbacks =
{
new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
var clientId = key.ToString()?.Replace("_public","").Replace("_private","");
if(string.IsNullOrEmpty(clientId)) { return; }
// Handle the eviction of the certificate - removing public/private keys from the cache
try{ cache.Remove($"{clientId}_public"); } catch{ }
try{ cache.Remove($"{clientId}_private"); } catch{ }
}
}
}
};
public string? Encrypt(string clientId,string plainText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
{
throw new InvalidOperationException("Public certificate not found for the client.");
}
// import rsa keys and configure RSA for encryption
using var rsa = RSA.Create(2048);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Encrypt the plain text using RSA
string? encryptedText = null;
try
{
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
var encryptedBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
encryptedText = Convert.ToBase64String(encryptedBytes);
}
catch (CryptographicException ex)
{
// Handle encryption errors
throw new InvalidOperationException("Encryption failed.", ex);
}
return encryptedText;
}
public string? Decrypt(string clientId, string encryptedText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
// import rsa keys and configure RSA for decryption
using var rsa = RSA.Create(2048);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Decrypt the encrypted text using RSA
string? plainText = null;
try
{
var encryptedBytes = Convert.FromBase64String(encryptedText);
var decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
plainText = System.Text.Encoding.UTF8.GetString(decryptedBytes);
}
catch (CryptographicException ex)
{
// Handle decryption errors
throw new InvalidOperationException("Decryption failed.", ex);
}
return plainText;
}
public string GetPublicCertificate(string clientId)
{
if (_cache.TryGetValue($"{clientId}_public", out string? publicCert) && !string.IsNullOrEmpty(publicCert))
{
return publicCert;
}
(publicCert, string privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return publicCert;
}
public string GetPrivateCertificate(string clientId)
{
if (_cache.TryGetValue($"{clientId}_private", out string? privateCert) && !string.IsNullOrEmpty(privateCert))
{
return privateCert;
}
(string publicCert, privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return privateCert;
}
private static (string publicCert, string privateCert) GenerateCertificate()
{
// Generate a new RSA key pair for the client
using var rsa = RSA.Create(2048);
var publicKey = rsa.ExportSubjectPublicKeyInfo();
var privateKey = rsa.ExportRSAPrivateKey();
// Convert to Base64 strings for storage
var publicCert = Convert.ToBase64String(publicKey);
var privateCert = Convert.ToBase64String(privateKey);
return (publicCert, privateCert);
}
public string? Hash(string plainText)
{
string? hash = null;
if (string.IsNullOrEmpty(plainText))
{
return hash;
}
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
var hashBytes = SHA256.HashData(plainBytes);
hash = Convert.ToBase64String(hashBytes);
return hash;
}
public bool VerifyHash(string plainText, string hash)
{
var plainTextHash = Hash(plainText);
if (string.IsNullOrEmpty(plainTextHash) || string.IsNullOrEmpty(hash))
{
return false;
}
return plainTextHash.Equals(hash, StringComparison.OrdinalIgnoreCase);
}
public string Pepper()
{
// get pepper from environtment variable
var pepper = Environment.GetEnvironmentVariable("PEPPER");
if (string.IsNullOrEmpty(pepper))
{
return "BactilForteFlash20mg";
}
return pepper;
}
public string Salt()
{
var saltBytes = new byte[32]; // 256 bits
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(saltBytes);
return Convert.ToBase64String(saltBytes);
}
public string? HashPassword(string plainPassword, string plainSalt)
{
return Hash($"{plainPassword}{plainSalt}{Pepper()}");
}
}

View File

@@ -1,16 +0,0 @@
using DependencyInjector.Abstractions.Lifetimes;
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();
string GetPublicCertificate(string clientId);
string GetPrivateCertificate(string clientId);
}

View File

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

View File

@@ -1,23 +0,0 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
namespace back.services.engine.ImageResizer;
public sealed class ImageResizer : IImageResizer
{
public async Task<Stream> ResizeImage(IFormFile image, int maxRes)
{
if (image == null || image.Length == 0)
{
throw new ArgumentException("Invalid image file.");
}
using var inputStream = image.OpenReadStream();
using var outputStream = new MemoryStream();
using var img = Image.Load(inputStream);
img.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(maxRes, 0), Mode = ResizeMode.Max }));
await img.SaveAsWebpAsync(outputStream);
outputStream.Position = 0;
return outputStream;
}
}

View File

@@ -1,8 +0,0 @@
using DependencyInjector.Abstractions.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

@@ -1,40 +0,0 @@
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 = "ABCÇDEFGHIJKLMNÑOPQRSTUVWXYZ";
const string minus = "abcçdefghijklmnñopqrstuvwxyz";
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)];
}
var positionPool = new List<int>();
for (int i = 0; i < length; i++) positionPool.Add(i);
var forcedRandomNumber = random.Next(0, positionPool.Count);
positionPool.RemoveAt(forcedRandomNumber);
var forcedRandomMayus = random.Next(0, positionPool.Count);
positionPool.RemoveAt(forcedRandomMayus);
var forcedRandomMinus = random.Next(0, positionPool.Count);
positionPool.RemoveAt(forcedRandomMinus);
var forcedRandomSpecial = random.Next(0, positionPool.Count);
positionPool.RemoveAt(forcedRandomSpecial);
password[forcedRandomNumber] = numbers[random.Next(numbers.Length)];
password[forcedRandomMayus] = mayus[random.Next(mayus.Length)];
password[forcedRandomMinus] = minus[random.Next(minus.Length)];
password[forcedRandomSpecial] = specials[random.Next(specials.Length)];
return new string(password);
}
}

View File

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

View File

@@ -1,60 +0,0 @@
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 MCVIngenieros.Transactional.Abstractions.Interfaces;
using System.Text.Json;
namespace back.services.engine.SystemUser;
public class SystemUserGenerator(
ITransactionalService<DataContext> transactional,
JsonSerializerOptions jsonSerializerOptions,
IUserRepository userRepository,
IPersonRepository personRepository,
IRoleRepository roleRepository,
IPermissionRepository permissionRepository,
ICryptoService cryptoService,
IBlobStorageService blobStorageService,
IPasswordGenerator passwordGenerator) : ISystemUserGenerator
{
public async Task GenerateAsync()
{
var systemKey = new SystemKey()
{
Password = passwordGenerator.Generate(16),
};
var systemKeyJson = JsonSerializer.Serialize(systemKey, options: jsonSerializerOptions);
using Stream stream = new MemoryStream(new System.Text.UTF8Encoding(true).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 permissionRepository.SeedDefaultPermissions();
await roleRepository.SeedDefaultRoles();
await personRepository.Insert(Person.SystemPerson);
await userRepository.Insert(User.SystemUser);
});
}
else
{
await userRepository.Update(User.SystemUser);
await userRepository.SaveChanges();
}
}
}

View File

@@ -1,66 +0,0 @@
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

@@ -1,9 +0,0 @@
using DependencyInjector.Abstractions.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

@@ -1,8 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace Presentation.Controllers;
public class AuthController : Controller
{
}

View File

@@ -1,6 +0,0 @@
namespace Presentation.Infraestructura.Responses;
public sealed class ExecutionError(string message)
{
public string Message { get; set; } = message;
}

View File

@@ -1,29 +0,0 @@
using System.Net;
namespace Presentation.Infraestructura.Responses;
public sealed class Response<T>
{
public bool IsSuccess { get; set; }
public HttpStatusCode StatusCode { get; set; }
public T? Data { get; set; }
public ValidationError[]? ValidationErrors { get; set; }
public ExecutionError[]? ExecutionErrors { get; set; }
public static Response<T> Success(T result, HttpStatusCode statusCode = HttpStatusCode.OK) =>
new()
{
IsSuccess = true,
StatusCode = statusCode,
Data = result
};
public static Response<T> Failure(HttpStatusCode statusCode = HttpStatusCode.InternalServerError, ValidationError[]? validationErrors = null, ExecutionError[]? executionErrors = null) =>
new()
{
IsSuccess = false,
StatusCode = statusCode,
ValidationErrors = validationErrors,
ExecutionErrors = executionErrors
};
}

View File

@@ -1,7 +0,0 @@
namespace Presentation.Infraestructura.Responses;
public sealed class ValidationError(string fieldName, string message)
{
public string Field { get; set; } = fieldName;
public string Message { get; set; } = message;
}

View File

@@ -1,34 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
<PackageReference Include="OpenTelemetry" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.2.0" />
</ItemGroup>
</Project>

View File

@@ -1,96 +0,0 @@
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.OpenTelemetry;
namespace Presentation
{
public class Program
{
public static void Main(string[] args)
{
// Configura Serilog como logger global antes de crear el builder
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.OpenTelemetry(options =>
{
options.Endpoint = "http://localhost:4317"; // OTLP endpoint
options.Protocol = OtlpProtocol.Grpc;
options.ResourceAttributes = new Dictionary<string, object>
{
["service.name"] = "mmorales.photo-backend"
};
})
.CreateLogger();
try
{
Log.Information("Starting up");
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(); // Usa Serilog como proveedor de logs por defecto
builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails =
ctx => ctx.ProblemDetails.Extensions.Add("traceId", ctx.HttpContext.TraceIdentifier)
);
builder.Services.AddOpenTelemetry()
.WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(builder.Environment.ApplicationName))
.AddAspNetCoreInstrumentation() // Traza todas las peticiones HTTP entrantes
.AddHttpClientInstrumentation() // Traza las llamadas HttpClient salientes
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri("http://localhost:4317"); // Direcci<63>n del colector OTel
})
.AddConsoleExporter(); // Exporta trazas tambi<62>n a consola para desarrollo
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}
app.UseStatusCodePages();
app.UseAuthentication(); // Habilita autenticaci<63>n
app.UseAuthorization(); // Habilita autorizaci<63>n
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
throw;
}
finally
{
Log.CloseAndFlush();
}
}
}
}

View File

@@ -1,23 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5101",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7265;http://localhost:5101",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,69 +0,0 @@
# Documentación del Frontend
## **Tecnología Base**
- **Framework:** Angular 20
- **Lenguaje:** TypeScript
- **Estilos:** SCSS
- **Gestión de Estado:** RxJS (si aplica)
- **Rutas:** Angular Router
## **Estructura del Proyecto**
El proyecto estará organizado en módulos y componentes para maximizar la reutilización y la escalabilidad. La estructura inicial será:
```FileSystem
src/
app/
core/ # Servicios y lógica compartida
shared/ # Componentes y directivas reutilizables
features/ # Módulos específicos de características
home/ # Página principal
gallery/ # Página de galería
profile/ # Página de perfil
app-routing.module.ts
app.module.ts
assets/ # Recursos estáticos como imágenes
environments/ # Configuraciones de entorno
```
## **Componentes Principales**
### **Menú de Navegación (`MenuComponent`)**
- **Descripción:** Componente para la navegación entre las diferentes secciones de la aplicación.
- **Propiedades:**
- `links: Array<{ label: string, route: string }>`: Lista de enlaces.
- **Eventos:**
- `onLinkClick`: Evento emitido al hacer clic en un enlace.
### **Galería (`GalleryComponent`)**
- **Descripción:** Componente para mostrar una colección de imágenes.
- **Propiedades:**
- `images: Array<{ src: string, alt: string }>`: Lista de imágenes.
- **Eventos:**
- `onImageClick`: Evento emitido al seleccionar una imagen.
### **Imagen (`ImageComponent`)**
- **Descripción:** Componente para mostrar una imagen individual.
- **Propiedades:**
- `src: string`: URL de la imagen.
- `alt: string`: Texto alternativo.
- **Eventos:**
- `onClick`: Evento emitido al hacer clic en la imagen.
### **Pie de Página (`FooterComponent`)**
- **Descripción:** Componente para mostrar información adicional en la parte inferior de la página.
- **Propiedades:**
- `links: Array<{ label: string, url: string }>`: Enlaces a recursos externos.
### **Página Principal (`HomePageComponent`)**
- **Descripción:** Página que combina el menú, la galería y el pie de página.
- **Componentes Hijos:**
- `MenuComponent`
- `GalleryComponent`
- `FooterComponent`

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