diff --git a/.gitea/workflows/deploy-docs-dev.yaml b/.gitea/workflows/deploy-docs-dev.yaml
new file mode 100644
index 0000000..a581978
--- /dev/null
+++ b/.gitea/workflows/deploy-docs-dev.yaml
@@ -0,0 +1,222 @@
+name: Deploy Documentation Local
+run-name: Deploying ${{ gitea.repository }} docs locally
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - dev
+ paths:
+ - "docs/**"
+
+jobs:
+ deploy-docs:
+ runs-on: windows:host # Ejecutar directamente en el host Windows
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+
+ - name: Setup Python for MkDocs
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+
+ - name: Install MkDocs and dependencies
+ run: |
+ pip install mkdocs mkdocs-material
+
+ - name: Build documentation
+ run: |
+ cd docs
+ mkdocs build --site-dir ../build
+
+ - name: Deploy to IIS directory
+ shell: powershell
+ run: |
+ $projectName = "${{ gitea.repository_name }}"
+ $basePath = "${{ secrets.DEPLOY_BASE_PATH }}"
+ $targetPath = Join-Path $basePath "dev" $projectName
+
+ # Crear directorio del proyecto si no existe
+ if (Test-Path $targetPath) {
+ Remove-Item -Path $targetPath -Recurse -Force
+ }
+ New-Item -ItemType Directory -Path $targetPath -Force
+
+ # Copiar archivos construidos
+ Copy-Item -Path "build\*" -Destination $targetPath -Recurse -Force
+
+ Write-Host "Documentation deployed to: $targetPath"
+
+ - name: Generate main index.html
+ shell: powershell
+ run: |
+ $docsPath = "${{ secrets.DEPLOY_BASE_PATH }}"
+ $docsPath = Join-Path $docsPath "dev"
+ $indexPath = Join-Path $docsPath "index.html"
+
+ # Obtener todos los directorios de documentación
+ $docFolders = Get-ChildItem -Path $docsPath -Directory | Sort-Object Name
+
+ # Generar HTML del índice
+ $htmlContent = @"
+
+
+
+
+
+ Documentación - MCV Ingenieros
+
+
+
+
+
+
+
+ "@
+
+ foreach ($folder in $docFolders) {
+ $folderName = $folder.Name
+ $lastWrite = $folder.LastWriteTime.ToString("dd/MM/yyyy HH:mm")
+
+ $htmlContent += @"
+
+ "@
+ }
+
+ $currentDate = (Get-Date).ToString("dd/MM/yyyy HH:mm")
+ $htmlContent += @"
+
+
+
+
+
+
+ "@
+
+ # Escribir el archivo index.html
+ Set-Content -Path $indexPath -Value $htmlContent -Encoding UTF8
+ Write-Host "Index.html actualizado en: $indexPath"
diff --git a/.gitea/workflows/deploy-docs.yaml b/.gitea/workflows/deploy-docs.yaml
new file mode 100644
index 0000000..0c6ac54
--- /dev/null
+++ b/.gitea/workflows/deploy-docs.yaml
@@ -0,0 +1,220 @@
+name: Deploy Documentation Local
+run-name: Deploying ${{ gitea.repository }} docs locally
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - "docs/**"
+
+jobs:
+ deploy-docs:
+ runs-on: windows:host # Ejecutar directamente en el host Windows
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+
+ - name: Setup Python for MkDocs
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+
+ - name: Install MkDocs and dependencies
+ run: |
+ pip install mkdocs mkdocs-material
+
+ - name: Build documentation
+ run: |
+ cd docs
+ mkdocs build --site-dir ../build
+
+ - name: Deploy to IIS directory
+ shell: powershell
+ run: |
+ $projectName = "${{ gitea.repository_name }}"
+ $basePath = "${{ secrets.DEPLOY_BASE_PATH }}"
+ $targetPath = Join-Path $basePath $projectName
+
+ # Crear directorio del proyecto si no existe
+ if (Test-Path $targetPath) {
+ Remove-Item -Path $targetPath -Recurse -Force
+ }
+ New-Item -ItemType Directory -Path $targetPath -Force
+
+ # Copiar archivos construidos
+ Copy-Item -Path "build\*" -Destination $targetPath -Recurse -Force
+
+ Write-Host "Documentation deployed to: $targetPath"
+
+ - name: Generate main index.html
+ shell: powershell
+ run: |
+ $docsPath = "${{ secrets.DEPLOY_BASE_PATH }}"
+ $indexPath = Join-Path $docsPath "index.html"
+
+ # Obtener todos los directorios de documentación
+ $docFolders = Get-ChildItem -Path $docsPath -Directory | Where-Object { $_.Name -notin @("dev", "test") } | Sort-Object Name
+
+ # Generar HTML del índice
+ $htmlContent = @"
+
+
+
+
+
+ Documentación - MCV Ingenieros
+
+
+
+
+
+
+
+ "@
+
+ foreach ($folder in $docFolders) {
+ $folderName = $folder.Name
+ $lastWrite = $folder.LastWriteTime.ToString("dd/MM/yyyy HH:mm")
+
+ $htmlContent += @"
+
+ "@
+ }
+
+ $currentDate = (Get-Date).ToString("dd/MM/yyyy HH:mm")
+ $htmlContent += @"
+
+
+
+
+
+
+ "@
+
+ # Escribir el archivo index.html
+ Set-Content -Path $indexPath -Value $htmlContent -Encoding UTF8
+ Write-Host "Index.html actualizado en: $indexPath"
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 49e0f5e..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -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"]
- }
- ]
-}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index c3feb3c..0000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -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"
- }
- }
- ]
-}
diff --git a/README.md b/README.md
index 954a514..abc6f6c 100644
--- a/README.md
+++ b/README.md
@@ -1,213 +1,156 @@
-# 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
-
-
-
-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.
+# Galerías Fotográficas
---
-## Glossary
+## Que problema identificamos
-__Frontend:__ The part of the application that users interact with directly, typically the website or app interface.
+Existen multiples alternativas para la distribución de imágenes digitales, como redes sociales o proveedores de almacenamiento en la nube.
+Las redes sociales exponen publicamente y con poco control el contenido de las imágenes.
+Los proveedores de almacenamiento ofrecen un control mayor sobre la exposición de las imágenes a cambio de una suscripción o pago por uso.
-__Backend:__ The server-side part of the application that handles business logic, data storage, and communication with other services.
+Como profesional que espera cobrar por el trabajo, publicar fotos privadas en redes sociales abre camino a múltiples problemas: perdida de privacidad para el cliente, mala exposición y pobre posicionamiento web, perdida de calidad en la imágen, se impide reutilizar la imagen con otros fines sin herramientas de terceros que pueden ser perjudiciales para clientes y profesionales.
+Usar proveedores como WeTransfer, Google Drive o Dropbox, requiere de pagos y un conocimiento mínimo sobre el uso de estos servicios por parte del cliente; además, para ahorrar costes los profesionales deben eliminar los grupos de imágenes con cierta frequencia o fracturar por tiempo de almacenamiento al cliente.
+Ambas opciones limitan la cooperatividad y la recuperación de las imágenes por parte del cliente.
+Además, expone fácilmente al profesional a varios riesgos y problemáticas emergentes con la evolución constante del software: la perdida de credenciales, el robo de identidad, el plagio, la pérdida de control sobre la calidad de transmisión de la imagen.
-__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.
+Como cliente, recibir una sesión por WeTransfer limita las opciones de feedback y mejora o personalización. Recibirla por correo electrónico limita la cantidad de imágenes recibidas. Recibirlas por Google Drive o similares nos quita espacio de nuestro almacenamiento en la nube. Si el profesional publica las imágenes, referenciándos puede afectar a nuestra huella digital e imagen en redes.
+Además, según la configuración del servicio que use el profesional, es común tener a disposición las imágenes solamente durante un tiempo determinado, haciendo imposible recuperarlas o acceder a ellas pasado ese tiempo.
-__IaaS (Infrastructure as a Service):__ A form of cloud computing that provides virtualized computing resources over the internet.
+Ambos participantes de la actividad recaen en un moderno problema: necesitar diversos servicios para controlar un único recurso.
+Como profesional, necesitas una web-portfolio, un perfil en redes, un servicio de almacenamiento y una forma de contacto con el cliente.
+Como cliente, necesitas poder ver el trabajo anterior del profesional, contactar con él y poder revisar el trabajo de forma conjunta para lograr el resultado que uno espera.
-__Client-side rendering:__ Rendering of web pages in the user's browser using JavaScript frameworks.
+## Que queremos resolver
-__Server-side rendering (SSR):__ Rendering of web pages on the server before sending them to the user's browser.
+Desde el punto de vista del profesional, podemos agrupar todos los servicios en un único servico.
+Una especie de Amazon para imágenes.
-__OpenID:__ An authentication protocol that allows users to log in to multiple services with a single identity.
+Desde el punto de vista del cliente, agrupandolo todo, evitamos la necesidad de acceder a tantos servicios diferentes y familiarizamos al usuario con una interfaz sencilla y directa, minimizando el roce con el aplicativo.
-__JWE (JSON Web Encryption):__ A standard for encrypting data in JSON format, often used for secure transmission of authentication tokens.
+## Alternativas a nuestra solución
-__MFA (Multi-Factor Authentication):__ A security system that requires more than one method of authentication from independent categories of credentials.
+Existen multitud de alternativas a una web hecha a mano, como Wix o Joomla.
+Existen alternativas cloud-native para alojar un portfolio.
+Existen muchos proveedores de almacenamiento web.
+Existen muchas redes sociales donde compartir las imágenes.
+Existen metodos de comunicación y trabajo colaborativo.
+En su gran mayoría, en servicios diferenciados.
+Sin embargo no existe servicios unificados.
-__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.
+## Despiece del problema
-__EntityFramework:__ An object-relational mapper (ORM) for .NET, used to interact with databases using .NET objects.
+### Almacenamiento
-__Blob Storage:__ A service for storing large amounts of unstructured data, such as images, videos, and documents.
+El profesional no quiere tener que gastar en almacenamiento ni preocuparse del estado de un proveedor.
+Por tanto, el sistema tiene que poder almacenar las imágenes durante mucho tiempo y mantenerlas en linea el mismo tiempo.
-__Plug-and-play architecture:__ A design approach that allows components to be easily added, removed, or replaced without affecting the rest of the system.
+### Distribución
-__CDN (Content Delivery Network):__ A network of servers distributed geographically to deliver content more efficiently to users based on their location.
+El profesional no quiere tener que usar más de un servicio para enviar las imágenes al cliente o subirlas a redes sociales.
+El cliente no quiere recibir las imágenes en un zip o tener que descargarlas desde un servicio de terceros.
-__AES (Advanced Encryption Standard):__ A symmetric encryption algorithm widely used for securing sensitive data.
+### Trabajo colaborativo
-__REST (Representational State Transfer):__ An architectural style for designing networked applications, often used for APIs.
+El profesional quiere que el cliente escoja la imagen que más el guste y le de feedback sobre el retoque y la edición de la imágen.
+El cliente queire revisar el trabajo del profesional para escoger las imágenes que más le gusten.
-__DAPR (Distributed Application Runtime):__ A runtime that simplifies building distributed systems by providing APIs for common tasks like state management and pub/sub messaging.
+### Feedback
-__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.
+El cliente puede dar feedback mediante comentarios en cada imágen como votando la imagen.
+La nota asignada a cada imágen se determinará como: (votos positivos - votos negativos) / votos totales
+Los comentarios se puntuarán de la misma forma.
+El profesional podrá responder a los comentarios y los leerá de forma que el comentario con mejor puntuación quede el primero, en su defecto se ordenarán por fecha.
-__Parallel Processing:__ A method of processing data in which multiple processors execute tasks simultaneously to increase efficiency and performance.
+El feedback también podrá ser dado a imágenes y colecciones del portfolio público, solamente lo podrán hacer los clientes autenticados. Aquellos que adquieran una sesión podrán dejar feedback al profesional de forma pública.
-__API (Application Programming Interface):__ Un conjunto de definiciones y protocolos para construir e integrar software de aplicaciones.
+### Portfolio
-__OAuth:__ Un estándar abierto para la autorización que permite a los usuarios compartir recursos entre aplicaciones sin compartir credenciales.
+El profesional no quiere gastar en otro servicio más para alojar el protfolio.
+El profesional quiere subir las imágenes al servicio y escoger cuales van a conformar el portfolio.
+El profesional quiere que la primera ojeada que tenga el cliente sobre el servicio sea el portfolio.
-__CI/CD (Continuous Integration/Continuous Deployment):__ Un conjunto de prácticas que automatizan el desarrollo, las pruebas y la implementación de software.
+### Colecciones
-__Microservicios:__ Un estilo arquitectónico que estructura una aplicación como un conjunto de servicios pequeños y autónomos.
+El profesional quiere organizar las imágenes por fecha, personas que salen en ellas, categorías, eventos...
+El profesional quiere poder reunir imágenes que se relacionen entre sí por categoría, evento, fechas, motivos...
+El profesional quiere juntar todas las imágenes que van destinadas a un cliente.
-__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.
+### Sesiones
-__Cache:__ Un almacenamiento temporal de datos para acelerar el acceso a información frecuentemente utilizada.
+El profesional hará varias imágenes que revisará con el cliente.
+El cliente escogerá únicamente las mejoras y que más le gusten.
+El profesional revisará esas imágenes, con un límite numérico de imágenes totales.
+El cliente recibirá una colección de esas imágenes.
+Múltiples clientes y profesionales podrán trabajar en una única sesión.
+Durante el proceso, debe existir una colaboración mutua mediante feedback.
-__JWT (JSON Web Token):__ Un estándar para transmitir información de forma segura entre partes como un objeto JSON.
+### Multiples clientes
+
+El profesional quiere poder trabajar sobre un proyecto y poder enviar el resultado del proyecto a multiples clientes.
+
+### Venta del servicio
+
+El profesional quiere cobrar una imágen única, una o varias colecciones, o una sesión.
+Un cliente pagará por una imágen, una o diversas colección premaquetadas, y por una sesión.
+Muchos clientes podrán pagar conjuntamente el servicio.
+
+### Usuarios
+
+Existirán 4 tipos de usuarios:
+
+- Anonimo
+- Cliente
+- Profesional
+- Administrador
+
+El usuario anónimo podrá consultar el portfolio y ver los diferentes botones y enlaces para ver el resto de imágenes públicas, redes sociales, blog o identificarse.
+
+El cliente, o usuario identificado, podrá hacer lo mismo que el anónimo. También podrá comprar las diferentes imágenes y colecciones disponibles. Además, podrá contratar una sesión, interactuar con la sesión que tenga activa dando feedback o escogiendo las imágenes; y consultar pedidos anteriores para descargar las imágenes, publicarlas en redes sociales o compartirlas de forma directa con otros clientes identificados.
+
+El profesional, podrá editar las imágenes y colecciones públicas que haya en el portfolio; organizar nuevas sesiones, editar las sesiones en marcha y actualizarlas; crear y editar nuevas colecciones; crear y editar eventos y categorías.
+
+El administrador, podrá hacer lo mismo que el profesional y además podrá añadir profesionales y editar aspectos clave de la web.
+
+Para facilitar el registro y el inicio de sesión, los usuarios podrán iniciar sesión con Google, Microsoft, Meta y Apple. Además, contarán con la opción de usar correo y contraseña. Para ello nos adaptaremos a OpenId y OAuth 2.
+Todos los usuarios tendrán un método MFA como sms o passkeys.
+Aquellos usuarios que inicien sesión con contraseña, recibirán un magic link para entrar.
+
+### Conexiones lentas
+
+Todas las imágenes subidas se procesarán en 3 categorías:
+
+- Baja resolución: 360p, comprimidas en WebP
+- Media resolución: 720p, comprimidas en WebP
+- Resolución Nativa: nativa, sin compresión
+
+### Pagos
+
+Se establecerá como recomendación el uso de linkpay o servicios como google wallet.
+
+---
+
+## Tecnologías a usar
+
+Back:
+JWT, OpenId + OAuth 2 -> Duende Identity Server
+DDD con CQRS -> MediatR
+Entity Framework Core + sqlite -> Base de datos
+AutoMapper -> Mapeo de objetos
+Serilog + OpenTelemetry -> Logging y trazabilidad
+Scalar + OpenApi -> Documentación y pruebas de API
+FluentValidation -> Validación de modelos
+Redis -> Almacenamiento en caché
+mailkit -> Envío de correos electrónicos
+ImageSharp -> Procesamiento de imágenes
+
+Front:
+TailwindCSS -> Estilos
+Angular -> Framework
+RxJS -> Programación reactiva
+NgRx -> Manejo del estado
+scss -> Preprocesador CSS
+axios -> Cliente HTTP
+Vite -> Bundler
+Typescript -> Lenguaje
diff --git a/back/.config/dotnet-tools.json b/back/.config/dotnet-tools.json
deleted file mode 100644
index 837b189..0000000
--- a/back/.config/dotnet-tools.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "version": 1,
- "isRoot": true,
- "tools": {
- "dotnet-ef": {
- "version": "9.0.8",
- "commands": [
- "dotnet-ef"
- ],
- "rollForward": false
- }
- }
-}
\ No newline at end of file
diff --git a/back/.program_data/app.db-shm b/back/.program_data/app.db-shm
deleted file mode 100644
index fbabe1e..0000000
Binary files a/back/.program_data/app.db-shm and /dev/null differ
diff --git a/back/.program_data/app.db-wal b/back/.program_data/app.db-wal
deleted file mode 100644
index 9157b2c..0000000
Binary files a/back/.program_data/app.db-wal and /dev/null differ
diff --git a/back/.program_data/imgs/systemkey.lock b/back/.program_data/imgs/systemkey.lock
deleted file mode 100644
index 92b2ff1..0000000
--- a/back/.program_data/imgs/systemkey.lock
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "email": "sys@t.em",
- "key": "aa0e0979-99db-42e7-8b60-91c2d055b9d0",
- "password": "+z1L[oYUupZ>L{4a"
-}
\ No newline at end of file
diff --git a/back/DTO/PhotoFormModel.cs b/back/DTO/PhotoFormModel.cs
deleted file mode 100644
index 6a295e4..0000000
--- a/back/DTO/PhotoFormModel.cs
+++ /dev/null
@@ -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; }
-}
diff --git a/back/DTO/UserDto.cs b/back/DTO/UserDto.cs
deleted file mode 100644
index 084863a..0000000
--- a/back/DTO/UserDto.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using back.DataModels;
-
-namespace back.DTO;
-
-public class UserDto
-{
- public string Id { get; set; } = null!;
- public ICollection Roles { get; set; } = [];
-}
diff --git a/back/DataModels/EfmigrationsLock.cs b/back/DataModels/EfmigrationsLock.cs
deleted file mode 100644
index e9d01bc..0000000
--- a/back/DataModels/EfmigrationsLock.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace back.DataModels;
-
-public partial class EfmigrationsLock
-{
- public int Id { get; set; }
-
- public string Timestamp { get; set; } = null!;
-}
diff --git a/back/DataModels/Event.cs b/back/DataModels/Event.cs
deleted file mode 100644
index 81c9219..0000000
--- a/back/DataModels/Event.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using Transactional.Abstractions;
-
-namespace back.DataModels;
-
-[Table("Events")]
-public partial class Event : ISoftDeletable, IEquatable
-{
- [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 Galleries { get; set; } = [];
- public virtual ICollection Photos { get; set; } = [];
- public virtual ICollection 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();
- }
-}
-
diff --git a/back/DataModels/Gallery.cs b/back/DataModels/Gallery.cs
deleted file mode 100644
index b540d8e..0000000
--- a/back/DataModels/Gallery.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using Transactional.Abstractions;
-
-namespace back.DataModels;
-
-[Table("Galleries")]
-public partial class Gallery: IEquatable, 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 Photos { get; set; } = [];
- public virtual ICollection Tags { get; set; } = [];
- public virtual ICollection 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();
- }
-}
diff --git a/back/DataModels/Permission.cs b/back/DataModels/Permission.cs
deleted file mode 100644
index 6fa6251..0000000
--- a/back/DataModels/Permission.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace back.DataModels;
-
-[Table("Permissions")]
-public partial class Permission: IEquatable
-{
- [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 Roles { get; set; } = [];
-
- public override int GetHashCode() => HashCode.Combine(Id, Name);
-
- public override bool Equals(object? obj)
- => obj is Permission other && Equals(other);
- public bool Equals(Permission? other)
- {
- if (other is null) return false;
- if (ReferenceEquals(this, other)) return true;
- return
- Id == other.Id || GetHashCode() == other.GetHashCode();
- }
-
- // Static permissions
- public static readonly Permission ViewContentPermission = new() { Id = "1", Name = "VIEW_CONTENT", Description = "Permission to view content" };
- public static readonly Permission LikeContentPermission = new() { Id = "2", Name = "LIKE_CONTENT", Description = "Permission to like content" };
- public static readonly Permission EditContentPermission = new() { Id = "3", Name = "EDIT_CONTENT", Description = "Permission to edit content" };
- public static readonly Permission DeleteContentPermission = new() { Id = "4", Name = "DELETE_CONTENT", Description = "Permission to delete content" };
- public static readonly Permission CreateContentPermission = new() { Id = "5", Name = "CREATE_CONTENT", Description = "Permission to create new content" };
- public static readonly Permission EditUserPermission = new() { Id = "6", Name = "EDIT_USER", Description = "Permission to edit user" };
- public static readonly Permission DeleteUserPermission = new() { Id = "7", Name = "DELETE_USER", Description = "Permission to delete user" };
- public static readonly Permission DisableUserPermission = new() { Id = "8", Name = "DISABLE_USER", Description = "Permission to disable user" };
- public static readonly Permission CreateUserPermission = new() { Id = "9", Name = "CREATE_USER", Description = "Permission to create new user" };
- public static readonly Permission EditWebConfigPermission = new() { Id = "10", Name = "EDIT_WEB_CONFIG", Description = "Permission to edit web configuration" };
-}
diff --git a/back/DataModels/Person.cs b/back/DataModels/Person.cs
deleted file mode 100644
index 1c10a4b..0000000
--- a/back/DataModels/Person.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using Transactional.Abstractions;
-using Transactional.Abstractions.Interfaces;
-
-namespace back.DataModels;
-
-[Table("Persons")]
-public partial class Person: IEntity, 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 Photos { get; set; } = [];
- public virtual SocialMedia? SocialMedia { get; set; }
- public virtual User? User { get; set; }
- public virtual ICollection 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
- };
-}
\ No newline at end of file
diff --git a/back/DataModels/Photo.cs b/back/DataModels/Photo.cs
deleted file mode 100644
index 694ee86..0000000
--- a/back/DataModels/Photo.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using Transactional.Abstractions.Interfaces;
-
-namespace back.DataModels;
-
-[Table("Photos")]
-public partial class Photo : IEntity
-{
- [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 Galleries { get; set; } = [];
- public virtual ICollection People { get; set; } = [];
- public virtual ICollection Tags { get; set; } = [];
- public virtual ICollection 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);
- }
-}
\ No newline at end of file
diff --git a/back/DataModels/Ranking.cs b/back/DataModels/Ranking.cs
deleted file mode 100644
index 408e945..0000000
--- a/back/DataModels/Ranking.cs
+++ /dev/null
@@ -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
-{
- [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;
-}
diff --git a/back/DataModels/Role.cs b/back/DataModels/Role.cs
deleted file mode 100644
index a0466ad..0000000
--- a/back/DataModels/Role.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace back.DataModels;
-
-[Table("Roles")]
-public partial class Role : IEquatable
-{
- [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 InverseBaseRoleModel { get; set; } = [];
- public virtual ICollection Permissions { get; set; } = new HashSet();
- public virtual ICollection 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? permissions = null, Role? baseRoleModel = null)
- {
- Id = id;
- Name = name;
- Description = description;
- Permissions = permissions ?? [];
- if (baseRoleModel != null)
- {
- BaseRoleModel = baseRoleModel;
- BaseRoleModelId = baseRoleModel.Id;
- foreach (var permission in baseRoleModel.Permissions)
- {
- if (!Permissions.Any(p => p.Id == permission.Id))
- {
- Permissions.Add(permission);
- }
- }
- }
- }
-
- public override int GetHashCode() => HashCode.Combine(Id, Name);
-
- public override bool Equals(object? obj) => obj is Role otherEvent && Equals(otherEvent);
-
- public bool Equals(Role? other)
- {
- if (other is null) return false;
- if (ReferenceEquals(this, other)) return true;
- return Id == other.Id || GetHashCode() == other.GetHashCode();
- }
-
- public static readonly Role UserRole = new(
- "1", "User", "Role for regular users",
- [
- Permission.ViewContentPermission,
- Permission.LikeContentPermission
- ]
- );
-
- public static readonly Role ContentManagerRole = new(
- "2", "Content Manager", "Role for managing content",
- [
- Permission.CreateContentPermission,
- Permission.EditContentPermission,
- Permission.DeleteContentPermission
- ],
- UserRole
- );
-
- public static readonly Role AdminRole = new(
- "3", "Admin", "Administrator role with full permissions",
- [
- Permission.CreateUserPermission,
- Permission.DisableUserPermission,
- Permission.EditUserPermission,
- Permission.DeleteUserPermission,
- Permission.EditWebConfigPermission
- ],
- ContentManagerRole
- );
-}
diff --git a/back/DataModels/SocialMedia.cs b/back/DataModels/SocialMedia.cs
deleted file mode 100644
index 009a05c..0000000
--- a/back/DataModels/SocialMedia.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace back.DataModels;
-
-[Table("SocialMedia")]
-public partial class SocialMedia: IEquatable
-{
- [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 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();
- }
-}
diff --git a/back/DataModels/SystemKey.cs b/back/DataModels/SystemKey.cs
deleted file mode 100644
index abc9100..0000000
--- a/back/DataModels/SystemKey.cs
+++ /dev/null
@@ -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);
- }
-}
\ No newline at end of file
diff --git a/back/DataModels/Tag.cs b/back/DataModels/Tag.cs
deleted file mode 100644
index 94565ee..0000000
--- a/back/DataModels/Tag.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace back.DataModels;
-
-[Table("Tags")]
-public partial class Tag: IEquatable
-{
- [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 Events { get; set; } = [];
-
- public virtual ICollection Galleries { get; set; } = [];
-
- public virtual ICollection 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();
- }
-}
diff --git a/back/DataModels/User.cs b/back/DataModels/User.cs
deleted file mode 100644
index 5dbd692..0000000
--- a/back/DataModels/User.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using back.DTO;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-using Transactional.Abstractions.Interfaces;
-
-namespace back.DataModels;
-
-[Table("Users")]
-public class User : IEntity
-{
- [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 Galleries { get; set; } = [];
- public virtual ICollection GalleriesNavigation { get; set; } = [];
- public virtual ICollection Photos { get; set; } = [];
- public virtual ICollection 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
- };
-
- 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]
- };
-}
\ No newline at end of file
diff --git a/back/Options/DatabaseConfig.cs b/back/Options/DatabaseConfig.cs
deleted file mode 100644
index b39c84c..0000000
--- a/back/Options/DatabaseConfig.cs
+++ /dev/null
@@ -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; }
-}
diff --git a/back/Options/Databases.cs b/back/Options/Databases.cs
deleted file mode 100644
index f3bddbd..0000000
--- a/back/Options/Databases.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace back.Options;
-
-public sealed class Databases
-{
- public string? BaseDirectory { get; set; }
-}
diff --git a/back/Options/MailServerOptions.cs b/back/Options/MailServerOptions.cs
deleted file mode 100644
index f8f74fc..0000000
--- a/back/Options/MailServerOptions.cs
+++ /dev/null
@@ -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; }
-}
diff --git a/back/Program.cs b/back/Program.cs
deleted file mode 100644
index 61028ea..0000000
--- a/back/Program.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using back.ServicesExtensions;
-using 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.AddHealthChecks(options => {
- options.CacheDuration = TimeSpan.FromMinutes(30);
- options.Timeout = TimeSpan.FromSeconds(5);
- options.AssembliesToScan = [typeof(Program).Assembly];
- }).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();
- }
-}
diff --git a/back/Properties/launchSettings.json b/back/Properties/launchSettings.json
deleted file mode 100644
index 114d561..0000000
--- a/back/Properties/launchSettings.json
+++ /dev/null
@@ -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"
- }
- }
- }
-}
diff --git a/back/ServicesExtensions/DatabaseContexts.cs b/back/ServicesExtensions/DatabaseContexts.cs
deleted file mode 100644
index c982e3d..0000000
--- a/back/ServicesExtensions/DatabaseContexts.cs
+++ /dev/null
@@ -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();
-
- return services;
- }
- private static IServiceCollection AddContext(this IServiceCollection services)
- where T : DbContext
- {
- var config = services
- .BuildServiceProvider()
- .GetRequiredService>()
- .Get(DatabaseConfig.DataStorage);
-
- services.AddDbContext(options =>
- {
- options.UseDatabaseConfig(config);
- });
-
- using var scope = services.BuildServiceProvider().CreateScope();
- var context = scope.ServiceProvider
- .GetRequiredService();
- var isDevelopment = scope.ServiceProvider
- .GetRequiredService()
- .IsDevelopment();
-
- if (isDevelopment && !context.Database.HasPendingModelChanges())
- {
- context.Database.EnsureCreated();
- }
- else
- {
- context.Database.EnsureCreated();
- context.Database.Migrate();
- }
- context.SaveChanges();
-
- return services;
- }
-}
diff --git a/back/ServicesExtensions/DdContextOptionsBuilderExtensions.cs b/back/ServicesExtensions/DdContextOptionsBuilderExtensions.cs
deleted file mode 100644
index 367ad22..0000000
--- a/back/ServicesExtensions/DdContextOptionsBuilderExtensions.cs
+++ /dev/null
@@ -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()
- .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()
- .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();
-}
diff --git a/back/ServicesExtensions/Options.cs b/back/ServicesExtensions/Options.cs
deleted file mode 100644
index 686ebd1..0000000
--- a/back/ServicesExtensions/Options.cs
+++ /dev/null
@@ -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();
- string? baseDirectory = null;
-
- services.Configure(config.GetSection(nameof(Databases)));
- services.Configure(DatabaseConfig.DataStorage, config.GetSection(DatabaseConfig.DataStorage));
- services.Configure(DatabaseConfig.BlobStorage, config.GetSection(DatabaseConfig.BlobStorage));
- services.Configure(config.GetSection(nameof(MailServerOptions)));
-
- services.Configure(HealthChecksConfigs.Sqlite, config.GetSection(HealthChecksConfigs.Sqlite));
-
- services.PostConfigure(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.DataStorage, config =>
- {
- PostConfigureDatabaseConfig(config, baseDirectory);
- });
-
- services.PostConfigure(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
- );
- }
- }
- }
-}
diff --git a/back/ServicesExtensions/ServicesExtensions.cs b/back/ServicesExtensions/ServicesExtensions.cs
deleted file mode 100644
index 00101c8..0000000
--- a/back/ServicesExtensions/ServicesExtensions.cs
+++ /dev/null
@@ -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 Transactional.Abstractions.Interfaces;
-using 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, EntityFrameworkTransactionalService>();
-
- 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().GenerateAsync().Wait();
-
-
- return services;
- }
-}
diff --git a/back/appsettings.Development.json b/back/appsettings.Development.json
deleted file mode 100644
index 46009f1..0000000
--- a/back/appsettings.Development.json
+++ /dev/null
@@ -1,29 +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": {
- "Sqlite": {
- "RetryAttempts" : 2,
- "Timeout" : "00:05:00",
- "RetryDelay" : "00:00:10",
- "Severity": "Info"
- }
- }
-}
diff --git a/back/appsettings.Production.json b/back/appsettings.Production.json
deleted file mode 100644
index 0bc2373..0000000
--- a/back/appsettings.Production.json
+++ /dev/null
@@ -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}"
- }
- }
-}
diff --git a/back/appsettings.json b/back/appsettings.json
deleted file mode 100644
index 10f68b8..0000000
--- a/back/appsettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
diff --git a/back/back.csproj b/back/back.csproj
deleted file mode 100644
index f2b09dd..0000000
--- a/back/back.csproj
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
- net9.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/back/back.sln b/back/back.sln
deleted file mode 100644
index 65a05f5..0000000
--- a/back/back.sln
+++ /dev/null
@@ -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}") = "Transactional", "..\..\nuget\Transactional\Transactional.csproj", "{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjector", "..\..\nuget\DependencyInjector\DependencyInjector.csproj", "{DBDF84A4-235C-4F29-8626-5BD1DC255970}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "healthchecks", "..\..\nuget\healthchecks\healthchecks.csproj", "{B21E2BEF-17B7-4981-9843-C0CC36D67010}"
-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
- {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B21E2BEF-17B7-4981-9843-C0CC36D67010}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B21E2BEF-17B7-4981-9843-C0CC36D67010}.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
diff --git a/back/controllers/CryptoController.cs b/back/controllers/CryptoController.cs
deleted file mode 100644
index 90da9c6..0000000
--- a/back/controllers/CryptoController.cs
+++ /dev/null
@@ -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 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 });
- }
-}
diff --git a/back/controllers/PhotosController.cs b/back/controllers/PhotosController.cs
deleted file mode 100644
index 3be5755..0000000
--- a/back/controllers/PhotosController.cs
+++ /dev/null
@@ -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/
- [HttpGet]
- public async Task>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
- {
- (int totalItems, IEnumerable? pageData) = await _photoService.GetPage(page, pageSize);
- Response.Headers.Append("X-Total-Count", totalItems.ToString());
- return Ok(pageData);
- }
-
- // GET api//5
- [HttpGet("{id}/{res}")]
- public async Task 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/
- [HttpPost]
- public async Task 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/
- [HttpPut]
- public async Task Put([FromBody] Photo photo)
- {
- await _photoService.Update(photo);
- return NoContent();
- }
-
- // DELETE api//5
- [HttpDelete("{id}")]
- public async Task Delete(string id)
- {
- await _photoService.Delete(id);
- return NoContent();
- }
-}
diff --git a/back/controllers/UsersController.cs b/back/controllers/UsersController.cs
deleted file mode 100644
index 1568056..0000000
--- a/back/controllers/UsersController.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using back.DataModels;
-using back.DTO;
-using back.services.bussines;
-using back.services.bussines.UserService;
-using Mapster;
-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/
- //[HttpGet]
- //public async Task>> 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//5
- //[HttpGet("{id}")]
- //public async Task Get(Guid id)
- //{
- // var user = await _userContext.GetById(id);
- // if (user == null)
- // return NotFound();
- // return Ok(user);
- //}
-
- [HttpPost("[action]")]
- public async Task 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.Adapt());
- }
-
- 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 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/
- [HttpPost("[action]")]
- public async Task 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);
- }
- }
-}
diff --git a/back/healthchecks/options/HealthChecksConfigs.cs b/back/healthchecks/options/HealthChecksConfigs.cs
deleted file mode 100644
index 2c432ac..0000000
--- a/back/healthchecks/options/HealthChecksConfigs.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using HealthChecksConfigsBase = healthchecks.Options.HealthChecksConfigs;
-
-namespace back.healthchecks.Options;
-
-public partial class HealthChecksConfigs : HealthChecksConfigsBase
-{
- public const string Sqlite = "Sqlite";
-}
diff --git a/back/healthchecks/sqlite.cs b/back/healthchecks/sqlite.cs
deleted file mode 100644
index 5f22ce3..0000000
--- a/back/healthchecks/sqlite.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using back.Options;
-using healthchecks;
-using healthchecks.Abstracts;
-using back.healthchecks.Options;
-using Microsoft.Extensions.Options;
-
-namespace back.healthchecks;
-
-public class SqliteHealthCheck(IOptionsMonitor databaseConfig, IOptionsMonitor healthchecksConfig) : IHealthCheck
-{
- 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 Task 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, null)
- {
- Details = details,
- Severity = isHealthy ? HealthCheckSeverity.Info : HealthCheckSeverity.Critical
- });
- }
-}
diff --git a/back/persistance/blob/FileSystemImageStorageService.cs b/back/persistance/blob/FileSystemImageStorageService.cs
deleted file mode 100644
index ea482a6..0000000
--- a/back/persistance/blob/FileSystemImageStorageService.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using back.Options;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-
-namespace back.persistance.blob;
-
-public class FileSystemImageStorageService(
- IOptions systemOptions,
- IOptionsMonitor 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 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 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);
- }
-}
-
diff --git a/back/persistance/blob/IBlobStorageService.cs b/back/persistance/blob/IBlobStorageService.cs
deleted file mode 100644
index 1ca5f14..0000000
--- a/back/persistance/blob/IBlobStorageService.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using DependencyInjector.Lifetimes;
-
-namespace back.persistance.blob;
-
-public interface IBlobStorageService : ISingleton
-{
- Task Save(Stream blobStream, string fileName);
- Task GetStream(string fileName);
- Task GetBytes(string fileName);
- Task Delete(string fileName);
- Task Update(Stream blobStream, string fileName);
-}
-
diff --git a/back/persistance/data/DataContext.cs b/back/persistance/data/DataContext.cs
deleted file mode 100644
index 5e32618..0000000
--- a/back/persistance/data/DataContext.cs
+++ /dev/null
@@ -1,65 +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 options) : base(options) { }
-
- public virtual DbSet EfmigrationsLocks { get; set; }
-
- public virtual DbSet Events { get; set; }
-
- public virtual DbSet Galleries { get; set; }
-
- public virtual DbSet Permissions { get; set; }
-
- public virtual DbSet Persons { get; set; }
-
- public virtual DbSet Photos { get; set; }
-
- public virtual DbSet Rankings { get; set; }
-
- public virtual DbSet Roles { get; set; }
-
- public virtual DbSet SocialMedia { get; set; }
-
- public virtual DbSet Tags { get; set; }
-
- public virtual DbSet Users { get; set; }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity(entity =>
- {
- entity.ToTable("__EFMigrationsLock");
-
- entity.Property(e => e.Id).ValueGeneratedNever();
- });
-
- typeof(IRelationEstablisher).Assembly.GetExportedTypes()
- .Where(t => typeof(IRelationEstablisher).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
- .ToList()
- .ForEach(seederType =>
- {
- var relationEstablisher = (IRelationEstablisher?)Activator.CreateInstance(seederType);
- relationEstablisher?.EstablishRelation(modelBuilder);
- });
-
- //typeof(ISeeder).Assembly.GetExportedTypes()
- // .Where(t => typeof(ISeeder).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
- // .ToList()
- // .ForEach(seederType =>
- // {
- // var seeder = (ISeeder?)Activator.CreateInstance(seederType);
- // seeder?.Seed(modelBuilder);
- // });
-
- OnModelCreatingPartial(modelBuilder);
- }
-
- partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
-}
diff --git a/back/persistance/data/migrations/20250824120656_InitialSetup.Designer.cs b/back/persistance/data/migrations/20250824120656_InitialSetup.Designer.cs
deleted file mode 100644
index 6b5d8ce..0000000
--- a/back/persistance/data/migrations/20250824120656_InitialSetup.Designer.cs
+++ /dev/null
@@ -1,752 +0,0 @@
-//
-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
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
-
- modelBuilder.Entity("EventTag", b =>
- {
- b.Property("EventId")
- .HasColumnType("TEXT");
-
- b.Property("TagId")
- .HasColumnType("TEXT");
-
- b.HasKey("EventId", "TagId");
-
- b.HasIndex("TagId");
-
- b.ToTable("EventTags", (string)null);
- });
-
- modelBuilder.Entity("GalleryPhoto", b =>
- {
- b.Property("GalleryId")
- .HasColumnType("TEXT");
-
- b.Property("PhotoId")
- .HasColumnType("TEXT");
-
- b.HasKey("GalleryId", "PhotoId");
-
- b.HasIndex("PhotoId");
-
- b.ToTable("GalleryPhotos", (string)null);
- });
-
- modelBuilder.Entity("GalleryTag", b =>
- {
- b.Property("GalleryId")
- .HasColumnType("TEXT");
-
- b.Property("TagId")
- .HasColumnType("TEXT");
-
- b.HasKey("GalleryId", "TagId");
-
- b.HasIndex("TagId");
-
- b.ToTable("GalleryTags", (string)null);
- });
-
- modelBuilder.Entity("GalleryUserViewer", b =>
- {
- b.Property("GalleryId")
- .HasColumnType("TEXT");
-
- b.Property("UserId")
- .HasColumnType("TEXT");
-
- b.HasKey("GalleryId", "UserId");
-
- b.HasIndex("UserId");
-
- b.ToTable("GalleryUserViewers", (string)null);
- });
-
- modelBuilder.Entity("PhotoPerson", b =>
- {
- b.Property("PhotoId")
- .HasColumnType("TEXT");
-
- b.Property("PersonId")
- .HasColumnType("TEXT");
-
- b.HasKey("PhotoId", "PersonId");
-
- b.HasIndex("PersonId");
-
- b.ToTable("PhotoPersons", (string)null);
- });
-
- modelBuilder.Entity("PhotoTag", b =>
- {
- b.Property("PhotoId")
- .HasColumnType("TEXT");
-
- b.Property("TagId")
- .HasColumnType("TEXT");
-
- b.HasKey("PhotoId", "TagId");
-
- b.HasIndex("TagId");
-
- b.ToTable("PhotoTags", (string)null);
- });
-
- modelBuilder.Entity("PhotoUserBuyer", b =>
- {
- b.Property("PhotoId")
- .HasColumnType("TEXT");
-
- b.Property("UserId")
- .HasColumnType("TEXT");
-
- b.HasKey("PhotoId", "UserId");
-
- b.HasIndex("UserId");
-
- b.ToTable("PhotoUserBuyers", (string)null);
- });
-
- modelBuilder.Entity("RolePermission", b =>
- {
- b.Property("RoleId")
- .HasColumnType("TEXT");
-
- b.Property("PermissionId")
- .HasColumnType("TEXT");
-
- b.HasKey("RoleId", "PermissionId");
-
- b.HasIndex("PermissionId");
-
- b.ToTable("RolePermissions", (string)null);
- });
-
- modelBuilder.Entity("UserRole", b =>
- {
- b.Property("UserId")
- .HasColumnType("TEXT");
-
- b.Property("RoleId")
- .HasColumnType("TEXT");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.ToTable("UserRoles", (string)null);
- });
-
- modelBuilder.Entity("back.DataModels.EfmigrationsLock", b =>
- {
- b.Property("Id")
- .HasColumnType("INTEGER");
-
- b.Property("Timestamp")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.ToTable("__EFMigrationsLock", (string)null);
- });
-
- modelBuilder.Entity("back.DataModels.Event", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("CreatedBy")
- .HasColumnType("TEXT");
-
- b.Property("Date")
- .HasColumnType("TEXT");
-
- b.Property("DeletedAt")
- .HasColumnType("TEXT");
-
- b.Property("Description")
- .HasMaxLength(500)
- .HasColumnType("TEXT");
-
- b.Property("IsDeleted")
- .HasColumnType("INTEGER");
-
- b.Property("Location")
- .HasColumnType("TEXT");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(50)
- .HasColumnType("TEXT");
-
- b.Property("UpdatedAt")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("UpdatedBy")
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.ToTable("Events");
- });
-
- modelBuilder.Entity("back.DataModels.Gallery", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .HasColumnType("TEXT");
-
- b.Property("CreatedBy")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("DeletedAt")
- .HasColumnType("TEXT");
-
- b.Property("Description")
- .HasMaxLength(500)
- .HasColumnType("TEXT");
-
- b.Property("EventId")
- .HasColumnType("TEXT");
-
- b.Property("IsArchived")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0);
-
- b.Property("IsDeleted")
- .HasColumnType("INTEGER");
-
- b.Property("IsFavorite")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0);
-
- b.Property("IsPublic")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(1);
-
- b.Property("Title")
- .HasMaxLength(100)
- .HasColumnType("TEXT");
-
- b.Property("UpdatedAt")
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.HasIndex("CreatedBy");
-
- b.HasIndex("EventId");
-
- b.ToTable("Galleries");
- });
-
- modelBuilder.Entity("back.DataModels.Permission", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Description")
- .HasMaxLength(255)
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.ToTable("Permissions");
- });
-
- modelBuilder.Entity("back.DataModels.Person", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Avatar")
- .HasColumnType("TEXT");
-
- b.Property("Bio")
- .HasMaxLength(250)
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("DeletedAt")
- .HasColumnType("TEXT");
-
- b.Property("IsDeleted")
- .HasColumnType("INTEGER");
-
- b.Property("Name")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("TEXT");
-
- b.Property("ProfilePicture")
- .HasColumnType("TEXT");
-
- b.Property("SocialMediaId")
- .HasColumnType("TEXT");
-
- b.Property("UpdatedAt")
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.HasIndex("SocialMediaId");
-
- b.ToTable("Persons");
- });
-
- modelBuilder.Entity("back.DataModels.Photo", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .HasColumnType("TEXT");
-
- b.Property("CreatedBy")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Description")
- .HasMaxLength(500)
- .HasColumnType("TEXT");
-
- b.Property("EventId")
- .HasColumnType("TEXT");
-
- b.Property("Extension")
- .HasColumnType("TEXT");
-
- b.Property("HighResUrl")
- .HasColumnType("TEXT");
-
- b.Property("IsArchived")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0);
-
- b.Property("IsFavorite")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0);
-
- b.Property("IsPublic")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(1);
-
- b.Property("LowResUrl")
- .HasColumnType("TEXT");
-
- b.Property("MidResUrl")
- .HasColumnType("TEXT");
-
- b.Property("RankingId")
- .HasColumnType("TEXT");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("TEXT");
-
- b.Property("UpdatedAt")
- .HasColumnType("TEXT");
-
- b.Property("UpdatedBy")
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.HasIndex("CreatedBy");
-
- b.HasIndex("EventId");
-
- b.ToTable("Photos");
- });
-
- modelBuilder.Entity("back.DataModels.Ranking", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("DownVotes")
- .HasColumnType("INTEGER");
-
- b.Property("TotalVotes")
- .HasColumnType("INTEGER");
-
- b.Property("UpVotes")
- .HasColumnType("INTEGER");
-
- b.HasKey("Id");
-
- b.ToTable("Rankings");
- });
-
- modelBuilder.Entity("back.DataModels.Role", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("BaseRoleModelId")
- .HasColumnType("TEXT");
-
- b.Property("Description")
- .HasMaxLength(250)
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.HasIndex("BaseRoleModelId");
-
- b.ToTable("Roles");
- });
-
- modelBuilder.Entity("back.DataModels.SocialMedia", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("BlueSky")
- .HasColumnType("TEXT");
-
- b.Property("Discord")
- .HasColumnType("TEXT");
-
- b.Property("Facebook")
- .HasColumnType("TEXT");
-
- b.Property("Instagram")
- .HasColumnType("TEXT");
-
- b.Property("Linkedin")
- .HasColumnType("TEXT");
-
- b.Property("Other")
- .HasColumnType("TEXT");
-
- b.Property("Pinterest")
- .HasColumnType("TEXT");
-
- b.Property("Reddit")
- .HasColumnType("TEXT");
-
- b.Property("Tiktok")
- .HasColumnType("TEXT");
-
- b.Property("Twitter")
- .HasColumnType("TEXT");
-
- b.HasKey("Id");
-
- b.ToTable("SocialMedia");
- });
-
- modelBuilder.Entity("back.DataModels.Tag", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("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("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("CreatedAt")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Email")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Password")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("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
- }
- }
-}
diff --git a/back/persistance/data/migrations/20250824120656_InitialSetup.cs b/back/persistance/data/migrations/20250824120656_InitialSetup.cs
deleted file mode 100644
index 2c9b59c..0000000
--- a/back/persistance/data/migrations/20250824120656_InitialSetup.cs
+++ /dev/null
@@ -1,583 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace back.Migrations
-{
- ///
- public partial class InitialSetup : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "__EFMigrationsLock",
- columns: table => new
- {
- Id = table.Column(type: "INTEGER", nullable: false),
- Timestamp = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK___EFMigrationsLock", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "Events",
- columns: table => new
- {
- Id = table.Column(type: "TEXT", nullable: false),
- Title = table.Column(type: "TEXT", maxLength: 50, nullable: false),
- Description = table.Column(type: "TEXT", maxLength: 500, nullable: true),
- Date = table.Column(type: "TEXT", nullable: true),
- Location = table.Column(type: "TEXT", nullable: true),
- CreatedAt = table.Column(type: "TEXT", nullable: false),
- UpdatedAt = table.Column(type: "TEXT", nullable: false),
- CreatedBy = table.Column(type: "TEXT", nullable: true),
- UpdatedBy = table.Column(type: "TEXT", nullable: true),
- IsDeleted = table.Column(type: "INTEGER", nullable: false),
- DeletedAt = table.Column(type: "TEXT", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Events", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "Permissions",
- columns: table => new
- {
- Id = table.Column(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", maxLength: 100, nullable: false),
- Description = table.Column(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(type: "TEXT", nullable: false),
- TotalVotes = table.Column(type: "INTEGER", nullable: false),
- UpVotes = table.Column(type: "INTEGER", nullable: false),
- DownVotes = table.Column(type: "INTEGER", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Rankings", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "Roles",
- columns: table => new
- {
- Id = table.Column(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", maxLength: 100, nullable: false),
- Description = table.Column(type: "TEXT", maxLength: 250, nullable: true),
- BaseRoleModelId = table.Column(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(type: "TEXT", nullable: false),
- Facebook = table.Column(type: "TEXT", nullable: true),
- Instagram = table.Column(type: "TEXT", nullable: true),
- Twitter = table.Column(type: "TEXT", nullable: true),
- BlueSky = table.Column(type: "TEXT", nullable: true),
- Tiktok = table.Column(type: "TEXT", nullable: true),
- Linkedin = table.Column(type: "TEXT", nullable: true),
- Pinterest = table.Column(type: "TEXT", nullable: true),
- Discord = table.Column(type: "TEXT", nullable: true),
- Reddit = table.Column(type: "TEXT", nullable: true),
- Other = table.Column(type: "TEXT", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_SocialMedia", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "Tags",
- columns: table => new
- {
- Id = table.Column(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", maxLength: 25, nullable: false),
- CreatedAt = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Tags", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "RolePermissions",
- columns: table => new
- {
- RoleId = table.Column(type: "TEXT", nullable: false),
- PermissionId = table.Column(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(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", maxLength: 100, nullable: false),
- ProfilePicture = table.Column(type: "TEXT", nullable: true),
- Avatar = table.Column(type: "TEXT", nullable: true),
- SocialMediaId = table.Column(type: "TEXT", nullable: true),
- Bio = table.Column(type: "TEXT", maxLength: 250, nullable: true),
- CreatedAt = table.Column(type: "TEXT", nullable: false),
- UpdatedAt = table.Column(type: "TEXT", nullable: true),
- IsDeleted = table.Column(type: "INTEGER", nullable: false),
- DeletedAt = table.Column(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(type: "TEXT", nullable: false),
- TagId = table.Column(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(type: "TEXT", nullable: false),
- Title = table.Column(type: "TEXT", maxLength: 100, nullable: false),
- Description = table.Column