Compare commits
1 Commits
9d7d9d91cd
...
copia-mast
Author | SHA1 | Date | |
---|---|---|---|
3fc98d5084 |
@@ -1,35 +0,0 @@
|
||||
name: Cleanup old test branches
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cleanup_branch:
|
||||
runs-on: windows
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Delete test branches older than 7 days
|
||||
shell: powershell
|
||||
run: |
|
||||
# Obtener la fecha límite (7 días antes)
|
||||
$limitDate = (Get-Date).AddDays(-7)
|
||||
|
||||
# Obtener todas las ramas remotas test/*
|
||||
$branches = git branch -r | Where-Object { $_ -match 'origin/test/' }
|
||||
|
||||
foreach ($branch in $branches) {
|
||||
$branchName = $branch.Trim() -replace '^origin/', ''
|
||||
# Obtener fecha de creación de la rama (aproximación por el primer commit)
|
||||
$firstCommitDate = git log $branchName --reverse --format="%ci" | Select-Object -First 1
|
||||
$branchDate = Get-Date $firstCommitDate
|
||||
|
||||
if ($branchDate -lt $limitDate) {
|
||||
Write-Host "Eliminando rama $branchName creada el $branchDate"
|
||||
git push origin --delete $branchName
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
name: Create Test Construct
|
||||
run-name: Creating test construct
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to build"
|
||||
required: true
|
||||
default: "dev"
|
||||
schedule:
|
||||
- cron: "0 12 * * *"
|
||||
|
||||
jobs:
|
||||
create_test_branch_and_build:
|
||||
runs-on: windows
|
||||
|
||||
steps:
|
||||
- name: Checkout dev branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Create test branch with date
|
||||
id: create_branch
|
||||
shell: powershell
|
||||
run: |
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$branchName = "test/$date"
|
||||
git checkout -b $branchName
|
||||
git push origin $branchName
|
||||
Write-Output "::set-output name=branch::$branchName"
|
||||
|
||||
deploy_docs:
|
||||
needs: create_test_branch_and_build
|
||||
runs-on: windows
|
||||
steps:
|
||||
- name: Checkout test branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.create_branch.outputs.branch }}
|
||||
|
||||
- name: Deploy documentation
|
||||
uses: ./.gitea/workflows/deploy-docs.yaml
|
||||
with:
|
||||
branch: ${{ steps.create_branch.outputs.branch }}
|
||||
|
||||
deploy_back:
|
||||
needs: create_test_branch_and_build
|
||||
runs-on: windows
|
||||
steps:
|
||||
- name: Checkout test branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.create_branch.outputs.branch }}
|
||||
|
||||
- name: Deploy .net project
|
||||
uses: ./.gitea/workflows/deploy-back.yaml
|
||||
with:
|
||||
branch: ${{ steps.create_branch.outputs.branch }}
|
||||
|
||||
deploy_front:
|
||||
needs: create_test_branch_and_build
|
||||
runs-on: windows
|
||||
steps:
|
||||
- name: Checkout test branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.create_branch.outputs.branch }}
|
||||
|
||||
- name: Deploy front project
|
||||
uses: ./.gitea/workflows/deploy-front.yaml
|
||||
with:
|
||||
branch: ${{ steps.create_branch.outputs.branch }}
|
@@ -1,49 +0,0 @@
|
||||
name: Create Test Construct
|
||||
run-name: Creating test construct
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [dev, "test/**"]
|
||||
paths: ["back/**"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to deploy"
|
||||
required: true
|
||||
default: "dev"
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
runs-on: windows
|
||||
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# build .net project
|
||||
- name: Build .NET Project
|
||||
run: |
|
||||
dotnet restore
|
||||
dotnet build
|
||||
|
||||
# deploy .net to iis site with path = "D:\iis\es\mcvingenieros\mmorales.photo\back"
|
||||
- name: Deploy .net project to iis
|
||||
shell: powershell
|
||||
run: |
|
||||
# deploy to iis site back.mmorales.photo that has path = D:\iis\es\mcvingenieros\mmorales.photo\back\
|
||||
$sourcePath = "D:\iis\es\mcvingenieros\mmorales.photo\back\bin\Release\net9.0\publish"
|
||||
$destinationPath = "D:\iis\es\mcvingenieros\mmorales.photo\back\"
|
||||
|
||||
# Stop IIS site
|
||||
Stop-WebAppPool -Name "mmorales.photo.back"
|
||||
|
||||
# Remove old files
|
||||
Remove-Item -Path $destinationPath\* -Recurse -Force
|
||||
|
||||
# Copy new files
|
||||
Copy-Item -Path $sourcePath\* -Destination $destinationPath -Recurse
|
||||
|
||||
# Start IIS site
|
||||
Start-WebAppPool -Name "mmorales.photo.back"
|
@@ -1,16 +1,13 @@
|
||||
name: Deploy Documentation Local
|
||||
run-name: Deploying ${{ vars.GIT_REPOSITORY }} docs locally
|
||||
run-name: Deploying ${{ gitea.repository }} docs locally
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
paths: ["docs/**", "mkdocs.yml", ".gitea/workflows/deploy-docs-dev.yaml"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to deploy"
|
||||
required: true
|
||||
default: "dev"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
@@ -22,7 +19,7 @@ jobs:
|
||||
- name: Setup Python for MkDocs
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install MkDocs and dependencies
|
||||
run: |
|
||||
@@ -30,15 +27,15 @@ jobs:
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
ls
|
||||
cd docs
|
||||
mkdocs build --site-dir ../build
|
||||
|
||||
- name: Deploy to IIS directory
|
||||
shell: powershell
|
||||
run: |
|
||||
$projectName = "${{ vars.GIT_REPOSITORY }}"
|
||||
$basePath = Join-Path "${{ secrets.DEPLOY_BASE_PATH }}" "dev"
|
||||
$targetPath = Join-Path $basePath $projectName
|
||||
$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) {
|
||||
@@ -47,7 +44,7 @@ jobs:
|
||||
New-Item -ItemType Directory -Path $targetPath -Force
|
||||
|
||||
# Copiar archivos construidos
|
||||
Copy-Item -Path "..\build\*" -Destination $targetPath -Recurse -Force
|
||||
Copy-Item -Path "build\*" -Destination $targetPath -Recurse -Force
|
||||
|
||||
Write-Host "Documentation deployed to: $targetPath"
|
||||
|
||||
@@ -68,7 +65,7 @@ jobs:
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Documentacion - MCV Ingenieros</title>
|
||||
<title>Documentación - MCV Ingenieros</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -182,8 +179,8 @@ jobs:
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Centro de Documentacion</h1>
|
||||
<p>MCV Ingenieros - Documentacion de Proyectos</p>
|
||||
<h1>📚 Centro de Documentación</h1>
|
||||
<p>MCV Ingenieros - Documentación de Proyectos</p>
|
||||
</div>
|
||||
|
||||
<div class="projects">
|
||||
@@ -196,8 +193,10 @@ jobs:
|
||||
$htmlContent += @"
|
||||
<div class="project-card">
|
||||
<a href="./$folderName/" class="project-link">
|
||||
<span class="project-icon">📖</span>
|
||||
<h3 class="project-title">$folderName</h3>
|
||||
<div class="project-meta">
|
||||
<span>🕒</span>
|
||||
<span>Actualizado: $lastWrite</span>
|
||||
</div>
|
||||
</a>
|
||||
@@ -210,7 +209,7 @@ jobs:
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Indice generado automaticamente el $currentDate</p>
|
||||
<p>Índice generado automáticamente el $currentDate</p>
|
||||
<p>Powered by Gitea Actions & IIS</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,20 +2,12 @@ name: Deploy Documentation Local
|
||||
run-name: Deploying ${{ gitea.repository }} docs locally
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [master]
|
||||
paths: ["docs/**", "mkdocs.yml", ".gitea/workflows/deploy-docs.yaml"]
|
||||
push:
|
||||
branches: [master]
|
||||
paths: ["docs/**", "mkdocs.yml", ".gitea/workflows/deploy-docs.yaml"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to deploy"
|
||||
required: true
|
||||
default: "master"
|
||||
workflow_call:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
@@ -27,7 +19,7 @@ jobs:
|
||||
- name: Setup Python for MkDocs
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install MkDocs and dependencies
|
||||
run: |
|
||||
@@ -35,13 +27,13 @@ jobs:
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
ls
|
||||
cd docs
|
||||
mkdocs build --site-dir ../build
|
||||
|
||||
- name: Deploy to IIS directory
|
||||
shell: powershell
|
||||
run: |
|
||||
$projectName = "${{ vars.GIT_REPOSITORY }}"
|
||||
$projectName = "${{ gitea.repository_name }}"
|
||||
$basePath = "${{ secrets.DEPLOY_BASE_PATH }}"
|
||||
$targetPath = Join-Path $basePath $projectName
|
||||
|
||||
@@ -52,7 +44,7 @@ jobs:
|
||||
New-Item -ItemType Directory -Path $targetPath -Force
|
||||
|
||||
# Copiar archivos construidos
|
||||
Copy-Item -Path "..\build\*" -Destination $targetPath -Recurse -Force
|
||||
Copy-Item -Path "build\*" -Destination $targetPath -Recurse -Force
|
||||
|
||||
Write-Host "Documentation deployed to: $targetPath"
|
||||
|
||||
@@ -63,7 +55,7 @@ jobs:
|
||||
$indexPath = Join-Path $docsPath "index.html"
|
||||
|
||||
# Obtener todos los directorios de documentación
|
||||
$docFolders = Get-ChildItem -Path $docsPath -Directory | Sort-Object Name
|
||||
$docFolders = Get-ChildItem -Path $docsPath -Directory | Where-Object { $_.Name -notin @("dev", "test") } | Sort-Object Name
|
||||
|
||||
# Generar HTML del índice
|
||||
$htmlContent = @"
|
||||
@@ -72,7 +64,7 @@ jobs:
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Documentacion - MCV Ingenieros</title>
|
||||
<title>Documentación - MCV Ingenieros</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -186,8 +178,8 @@ jobs:
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Centro de Documentacion</h1>
|
||||
<p>MCV Ingenieros - Documentacion de Proyectos</p>
|
||||
<h1>📚 Centro de Documentación</h1>
|
||||
<p>MCV Ingenieros - Documentación de Proyectos</p>
|
||||
</div>
|
||||
|
||||
<div class="projects">
|
||||
@@ -200,8 +192,10 @@ jobs:
|
||||
$htmlContent += @"
|
||||
<div class="project-card">
|
||||
<a href="./$folderName/" class="project-link">
|
||||
<span class="project-icon">📖</span>
|
||||
<h3 class="project-title">$folderName</h3>
|
||||
<div class="project-meta">
|
||||
<span>🕒</span>
|
||||
<span>Actualizado: $lastWrite</span>
|
||||
</div>
|
||||
</a>
|
||||
@@ -214,7 +208,7 @@ jobs:
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Indice generado automaticamente el $currentDate</p>
|
||||
<p>Índice generado automáticamente el $currentDate</p>
|
||||
<p>Powered by Gitea Actions & IIS</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,48 +0,0 @@
|
||||
name: deploy front
|
||||
run-name: Deploy Frontend
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [dev, "test/**"]
|
||||
paths: ["front/**"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch to deploy"
|
||||
required: true
|
||||
default: "dev"
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
runs-on: windows
|
||||
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# build angular
|
||||
- name: Build Angular
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Deploy to IIS
|
||||
shell: powershell
|
||||
run: |
|
||||
# deploy to iis site front.mmorales.photo that has path = D:\iis\es\mcvingenieros\mmorales.photo\front\
|
||||
$sourcePath = "D:\iis\es\mcvingenieros\mmorales.photo\front\dist"
|
||||
$destinationPath = "D:\iis\es\mcvingenieros\mmorales.photo\front\"
|
||||
|
||||
# Stop IIS site
|
||||
Stop-WebAppPool -Name "mmorales.photo.front"
|
||||
|
||||
# Remove old files
|
||||
Remove-Item -Path $destinationPath\* -Recurse -Force
|
||||
|
||||
# Copy new files
|
||||
Copy-Item -Path $sourcePath\* -Destination $destinationPath -Recurse
|
||||
|
||||
# Start IIS site
|
||||
Start-WebAppPool -Name "mmorales.photo.front"
|
46
.vscode/launch.json
vendored
Normal file
46
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "FRONT: DEBUG(Edge)",
|
||||
"request": "launch",
|
||||
"type": "msedge",
|
||||
"url": "http://localhost:4200",
|
||||
"webRoot": "${workspaceFolder}/front/v2",
|
||||
"preLaunchTask": "Start Node server with nvs latest"
|
||||
},
|
||||
{
|
||||
"name": "(legacy) FRONT: DEBUG(Edge)",
|
||||
"request": "launch",
|
||||
"type": "msedge",
|
||||
"url": "http://localhost:4200",
|
||||
"webRoot": "${workspaceFolder}/front/v1",
|
||||
"preLaunchTask": "(legacy) Start Node server with nvs latest"
|
||||
},
|
||||
{
|
||||
"name": "Attach Edge",
|
||||
"type": "msedge",
|
||||
"request": "attach",
|
||||
"url": "http://localhost:4200/#",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Launch Edge (Test)",
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:9876/debug.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Launch Edge (E2E)",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/protractor/bin/protractor",
|
||||
"protocol": "inspector",
|
||||
"args": ["${workspaceFolder}/protractor.conf.js"]
|
||||
}
|
||||
]
|
||||
}
|
37
.vscode/tasks.json
vendored
Normal file
37
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
309
README.md
309
README.md
@@ -1,156 +1,213 @@
|
||||
# Galerías Fotográficas
|
||||
# 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.
|
||||
|
||||
---
|
||||
|
||||
## Que problema identificamos
|
||||
## Glossary
|
||||
|
||||
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.
|
||||
__Frontend:__ The part of the application that users interact with directly, typically the website or app interface.
|
||||
|
||||
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.
|
||||
__Backend:__ The server-side part of the application that handles business logic, data storage, and communication with other services.
|
||||
|
||||
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.
|
||||
__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.
|
||||
|
||||
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.
|
||||
__IaaS (Infrastructure as a Service):__ A form of cloud computing that provides virtualized computing resources over the internet.
|
||||
|
||||
## Que queremos resolver
|
||||
__Client-side rendering:__ Rendering of web pages in the user's browser using JavaScript frameworks.
|
||||
|
||||
Desde el punto de vista del profesional, podemos agrupar todos los servicios en un único servico.
|
||||
Una especie de Amazon para imágenes.
|
||||
__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 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.
|
||||
__OpenID:__ An authentication protocol that allows users to log in to multiple services with a single identity.
|
||||
|
||||
## Alternativas a nuestra solución
|
||||
__JWE (JSON Web Encryption):__ A standard for encrypting data in JSON format, often used for secure transmission of authentication tokens.
|
||||
|
||||
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.
|
||||
__MFA (Multi-Factor Authentication):__ A security system that requires more than one method of authentication from independent categories of credentials.
|
||||
|
||||
---
|
||||
__BFF (Backend for Frontend):__ An architectural pattern where a dedicated backend is built for each frontend application to optimize communication and security.
|
||||
|
||||
## Despiece del problema
|
||||
__Domain-Driven Design (DDD):__ An approach to software development that focuses on modeling software to match a domain's business concepts and logic.
|
||||
|
||||
### Almacenamiento
|
||||
__EntityFramework:__ An object-relational mapper (ORM) for .NET, used to interact with databases using .NET objects.
|
||||
|
||||
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.
|
||||
__Blob Storage:__ A service for storing large amounts of unstructured data, such as images, videos, and documents.
|
||||
|
||||
### Distribución
|
||||
__Plug-and-play architecture:__ A design approach that allows components to be easily added, removed, or replaced without affecting the rest of the system.
|
||||
|
||||
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.
|
||||
__CDN (Content Delivery Network):__ A network of servers distributed geographically to deliver content more efficiently to users based on their location.
|
||||
|
||||
### Trabajo colaborativo
|
||||
__AES (Advanced Encryption Standard):__ A symmetric encryption algorithm widely used for securing sensitive data.
|
||||
|
||||
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.
|
||||
__REST (Representational State Transfer):__ An architectural style for designing networked applications, often used for APIs.
|
||||
|
||||
### Feedback
|
||||
__DAPR (Distributed Application Runtime):__ A runtime that simplifies building distributed systems by providing APIs for common tasks like state management and pub/sub messaging.
|
||||
|
||||
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.
|
||||
__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 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.
|
||||
__Parallel Processing:__ A method of processing data in which multiple processors execute tasks simultaneously to increase efficiency and performance.
|
||||
|
||||
### Portfolio
|
||||
__API (Application Programming Interface):__ Un conjunto de definiciones y protocolos para construir e integrar software de aplicaciones.
|
||||
|
||||
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.
|
||||
__OAuth:__ Un estándar abierto para la autorización que permite a los usuarios compartir recursos entre aplicaciones sin compartir credenciales.
|
||||
|
||||
### Colecciones
|
||||
__CI/CD (Continuous Integration/Continuous Deployment):__ Un conjunto de prácticas que automatizan el desarrollo, las pruebas y la implementación de software.
|
||||
|
||||
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.
|
||||
__Microservicios:__ Un estilo arquitectónico que estructura una aplicación como un conjunto de servicios pequeños y autónomos.
|
||||
|
||||
### Sesiones
|
||||
__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.
|
||||
|
||||
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.
|
||||
__Cache:__ Un almacenamiento temporal de datos para acelerar el acceso a información frecuentemente utilizada.
|
||||
|
||||
### 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
|
||||
__JWT (JSON Web Token):__ Un estándar para transmitir información de forma segura entre partes como un objeto JSON.
|
||||
|
13
back/.config/dotnet-tools.json
Normal file
13
back/.config/dotnet-tools.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.8",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
5
back/.program_data/imgs/systemkey.lock
Normal file
5
back/.program_data/imgs/systemkey.lock
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"email": "sys@t.em",
|
||||
"key": "b60e166e-d4a5-416e-a7c9-142d05fb7f31",
|
||||
"password": "8C3,uTÑ<hñ61qQs3"
|
||||
}
|
14
back/DTO/PhotoFormModel.cs
Normal file
14
back/DTO/PhotoFormModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
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; }
|
||||
}
|
9
back/DTO/UserDto.cs
Normal file
9
back/DTO/UserDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using back.DataModels;
|
||||
|
||||
namespace back.DTO;
|
||||
|
||||
public class UserDto
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
public ICollection<RoleDto> Roles { get; set; } = [];
|
||||
}
|
8
back/DataModels/EfmigrationsLock.cs
Normal file
8
back/DataModels/EfmigrationsLock.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace back.DataModels;
|
||||
|
||||
public partial class EfmigrationsLock
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Timestamp { get; set; } = null!;
|
||||
}
|
44
back/DataModels/Event.cs
Normal file
44
back/DataModels/Event.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using MCVIngenieros.Transactional.Abstractions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Events")]
|
||||
public partial class Event : ISoftDeletable, IEquatable<Event>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(50)]
|
||||
public string Title { get; set; } = null!;
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
public string? Date { get; set; }
|
||||
public string? Location { get; set; }
|
||||
public string CreatedAt { get; set; } = null!;
|
||||
public string UpdatedAt { get; set; } = null!;
|
||||
public string? CreatedBy { get; set; }
|
||||
public string? UpdatedBy { get; set; }
|
||||
public int IsDeleted { get; set; }
|
||||
public string? DeletedAt { get; set; }
|
||||
public virtual ICollection<Gallery> Galleries { get; set; } = [];
|
||||
public virtual ICollection<Photo> Photos { get; set; } = [];
|
||||
public virtual ICollection<Tag> Tags { get; set; } = [];
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(Id, Title, Date, Location);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Event otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(Event? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id
|
||||
|| (Title == other.Title && Date == other.Date && Location == other.Location)
|
||||
|| GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
44
back/DataModels/Gallery.cs
Normal file
44
back/DataModels/Gallery.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using MCVIngenieros.Transactional.Abstractions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Galleries")]
|
||||
public partial class Gallery: IEquatable<Gallery>, ISoftDeletable
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[MaxLength(100)]
|
||||
public string? Title { get; set; }
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
public string? CreatedAt { get; set; }
|
||||
public string? UpdatedAt { get; set; }
|
||||
public string CreatedBy { get; set; } = null!;
|
||||
public int? IsPublic { get; set; }
|
||||
public int? IsArchived { get; set; }
|
||||
public int? IsFavorite { get; set; }
|
||||
public int IsDeleted { get; set; }
|
||||
public string? DeletedAt { get; set; }
|
||||
public string? EventId { get; set; }
|
||||
public virtual User CreatedByNavigation { get; set; } = null!;
|
||||
public virtual Event? Event { get; set; }
|
||||
public virtual ICollection<Photo> Photos { get; set; } = [];
|
||||
public virtual ICollection<Tag> Tags { get; set; } = [];
|
||||
public virtual ICollection<User> Users { get; set; } = [];
|
||||
|
||||
public Gallery() { }
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Title);
|
||||
|
||||
public override bool Equals(object? obj) => obj is Gallery otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(Gallery? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
}
|
72
back/DataModels/Permission.cs
Normal file
72
back/DataModels/Permission.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
public record PermissionDto
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
}
|
||||
|
||||
[Table("Permissions")]
|
||||
public partial class Permission : IEntity<Permission>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(100)]
|
||||
public string Name { get; set; } = null!;
|
||||
[MaxLength(255)]
|
||||
public string? Description { get; set; }
|
||||
public virtual ICollection<Role> Roles { get; set; } = [];
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Name);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Permission other && Equals(other);
|
||||
public bool Equals(Permission? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
|
||||
public bool IsNull => this is null;
|
||||
|
||||
public object Clone() => (Permission)MemberwiseClone();
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
if (obj is not Permission other) throw new ArgumentException("Object is not a Person");
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(Permission? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public PermissionDto ToDto()
|
||||
{
|
||||
return new PermissionDto
|
||||
{
|
||||
Id = Id
|
||||
};
|
||||
}
|
||||
|
||||
// Static permissions
|
||||
public static readonly Permission ViewContentPermission = new() { Id = "1", Name = "VIEW_CONTENT", Description = "Permission to view content" };
|
||||
public static readonly Permission LikeContentPermission = new() { Id = "2", Name = "LIKE_CONTENT", Description = "Permission to like content" };
|
||||
public static readonly Permission EditContentPermission = new() { Id = "3", Name = "EDIT_CONTENT", Description = "Permission to edit content" };
|
||||
public static readonly Permission DeleteContentPermission = new() { Id = "4", Name = "DELETE_CONTENT", Description = "Permission to delete content" };
|
||||
public static readonly Permission CreateContentPermission = new() { Id = "5", Name = "CREATE_CONTENT", Description = "Permission to create new content" };
|
||||
public static readonly Permission EditUserPermission = new() { Id = "6", Name = "EDIT_USER", Description = "Permission to edit user" };
|
||||
public static readonly Permission DeleteUserPermission = new() { Id = "7", Name = "DELETE_USER", Description = "Permission to delete user" };
|
||||
public static readonly Permission DisableUserPermission = new() { Id = "8", Name = "DISABLE_USER", Description = "Permission to disable user" };
|
||||
public static readonly Permission CreateUserPermission = new() { Id = "9", Name = "CREATE_USER", Description = "Permission to create new user" };
|
||||
public static readonly Permission EditWebConfigPermission = new() { Id = "10", Name = "EDIT_WEB_CONFIG", Description = "Permission to edit web configuration" };
|
||||
}
|
70
back/DataModels/Person.cs
Normal file
70
back/DataModels/Person.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using MCVIngenieros.Transactional.Abstractions;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Persons")]
|
||||
public partial class Person: IEntity<Person>, ISoftDeletable
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(100)]
|
||||
public string Name { get; set; } = null!;
|
||||
public string? ProfilePicture { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public string? SocialMediaId { get; set; }
|
||||
[MaxLength(250)]
|
||||
public string? Bio { get; set; } // Optional field for a short biography or description
|
||||
public string CreatedAt { get; set; } = null!;
|
||||
public string? UpdatedAt { get; set; }
|
||||
public int IsDeleted { get; set; }
|
||||
public string? DeletedAt { get; set; }
|
||||
|
||||
public virtual ICollection<Photo> Photos { get; set; } = [];
|
||||
public virtual SocialMedia? SocialMedia { get; set; }
|
||||
public virtual User? User { get; set; }
|
||||
public virtual ICollection<Photo> PhotosNavigation { get; set; } = [];
|
||||
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Name);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Person other && Equals(other);
|
||||
|
||||
public bool Equals(Person? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
public bool IsNull => this is null;
|
||||
|
||||
public object Clone() => (Person)MemberwiseClone();
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if(obj is null) return 1;
|
||||
if (obj is not Person other) throw new ArgumentException("Object is not a Person");
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(Person? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public const string SystemPersonId = "00000000-0000-0000-0000-000000000001";
|
||||
|
||||
public static readonly Person SystemPerson = new()
|
||||
{
|
||||
Id = SystemPersonId,
|
||||
Name = "System",
|
||||
CreatedAt = DateTime.UtcNow.ToString("dd-MM-yyyy HH:mm:ss zz"),
|
||||
User = User.SystemUser
|
||||
};
|
||||
}
|
66
back/DataModels/Photo.cs
Normal file
66
back/DataModels/Photo.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Photos")]
|
||||
public partial class Photo : IEntity<Photo>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(100), MinLength(1)]
|
||||
public string Title { get; set; } = null!;
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
public string? Extension { get; set; }
|
||||
public string? LowResUrl { get; set; }
|
||||
public string? MidResUrl { get; set; }
|
||||
public string? HighResUrl { get; set; }
|
||||
public string? CreatedAt { get; set; }
|
||||
public string? UpdatedAt { get; set; }
|
||||
public string CreatedBy { get; set; } = null!;
|
||||
public string? UpdatedBy { get; set; }
|
||||
public string? EventId { get; set; }
|
||||
public string? RankingId { get; set; }
|
||||
public int? IsFavorite { get; set; }
|
||||
public int? IsPublic { get; set; }
|
||||
public int? IsArchived { get; set; }
|
||||
public virtual Person CreatedByNavigation { get; set; } = null!;
|
||||
public virtual Event? Event { get; set; }
|
||||
public virtual ICollection<Gallery> Galleries { get; set; } = [];
|
||||
public virtual ICollection<Person> People { get; set; } = [];
|
||||
public virtual ICollection<Tag> Tags { get; set; } = [];
|
||||
public virtual ICollection<User> Users { get; set; } = [];
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Title);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Photo otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(Photo? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
|
||||
public bool IsNull => this is null;
|
||||
|
||||
public object Clone() => (Photo)MemberwiseClone();
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
if (obj is not Photo other) throw new ArgumentException("Object is not a Person");
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(Photo? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
119
back/DataModels/Ranking.cs
Normal file
119
back/DataModels/Ranking.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
public class RankingGroup
|
||||
{
|
||||
public const float ExtremlyBad = 1/5f;
|
||||
public const float Bad = 2 / 5f;
|
||||
public const float Normal = 3 / 5f;
|
||||
public const float Good = 4 / 5f;
|
||||
public const float ExtremlyGood = 5 / 5f;
|
||||
|
||||
public static string GetGroup(float score) => (float)Math.Ceiling(score) switch
|
||||
{
|
||||
<= ExtremlyBad => nameof(ExtremlyBad),
|
||||
<= Bad => nameof(Bad),
|
||||
<= Normal => nameof(Normal),
|
||||
<= Good => nameof(Good),
|
||||
_ => nameof(ExtremlyGood)
|
||||
};
|
||||
}
|
||||
|
||||
[Table("Rankings")]
|
||||
public partial class Ranking : IEquatable<Ranking>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
public int TotalVotes { get; set; }
|
||||
public int UpVotes { get; set; }
|
||||
public int DownVotes { get; set; }
|
||||
|
||||
public Ranking(int totalVotes, int upVotes = 0, int downVotes = 0)
|
||||
{
|
||||
TotalVotes = totalVotes;
|
||||
UpVotes = upVotes;
|
||||
DownVotes = downVotes;
|
||||
}
|
||||
|
||||
public Ranking()
|
||||
{
|
||||
TotalVotes = 0;
|
||||
UpVotes = 0;
|
||||
DownVotes = 0;
|
||||
}
|
||||
|
||||
public void DownVote()
|
||||
{
|
||||
DownVotes++;
|
||||
TotalVotes++;
|
||||
}
|
||||
|
||||
public void UpVote()
|
||||
{
|
||||
UpVotes++;
|
||||
TotalVotes++;
|
||||
}
|
||||
|
||||
public float Score
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TotalVotes == 0) return 0;
|
||||
return (float)(UpVotes - DownVotes) / TotalVotes;
|
||||
}
|
||||
}
|
||||
|
||||
public string Group => RankingGroup.GetGroup(Score);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, TotalVotes, UpVotes, DownVotes);
|
||||
|
||||
public override bool Equals(object? obj) => obj is Ranking otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(Ranking? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return
|
||||
Id == other.Id
|
||||
|| GetHashCode() == other.GetHashCode()
|
||||
|| (TotalVotes == other.TotalVotes && UpVotes == other.UpVotes && DownVotes == other.DownVotes);
|
||||
}
|
||||
|
||||
public static bool operator ==(Ranking ranking1, Ranking ranking2)
|
||||
{
|
||||
if (ranking1 is null && ranking2 is null) return true;
|
||||
if (ranking1 is null || ranking2 is null) return false;
|
||||
return ranking1.Equals(ranking2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Ranking ranking1, Ranking ranking2)
|
||||
{
|
||||
if (ranking1 is null && ranking2 is null) return false;
|
||||
if (ranking1 is null || ranking2 is null) return true;
|
||||
return !ranking1.Equals(ranking2);
|
||||
}
|
||||
|
||||
public static bool operator < (Ranking ranking1, Ranking ranking2)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ranking1, nameof(ranking1));
|
||||
ArgumentNullException.ThrowIfNull(ranking2, nameof(ranking2));
|
||||
return ranking1.Score < ranking2.Score;
|
||||
}
|
||||
|
||||
public static bool operator > (Ranking ranking1, Ranking ranking2)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ranking1, nameof(ranking1));
|
||||
ArgumentNullException.ThrowIfNull(ranking2, nameof(ranking2));
|
||||
if (ranking1 is null && ranking2 is null) return true;
|
||||
if (ranking1 is null || ranking2 is null) return false;
|
||||
return ranking1.Score > ranking2.Score;
|
||||
}
|
||||
|
||||
public static bool operator <= (Ranking ranking1, Ranking ranking2)
|
||||
=> ranking1 == ranking2 || ranking1 < ranking2;
|
||||
|
||||
public static bool operator >= (Ranking ranking1, Ranking ranking2)
|
||||
=> ranking1 == ranking2 || ranking1 > ranking2;
|
||||
}
|
127
back/DataModels/Role.cs
Normal file
127
back/DataModels/Role.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
public class RoleDto
|
||||
{
|
||||
public string Id { get; set; } = null!;
|
||||
public List<PermissionDto> Permissions { get; set; } = [];
|
||||
}
|
||||
|
||||
[Table("Roles")]
|
||||
public partial class Role : IEntity<Role>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(100)]
|
||||
public string Name { get; set; } = null!;
|
||||
[MaxLength(250)]
|
||||
public string? Description { get; set; }
|
||||
public string? BaseRoleModelId { get; set; }
|
||||
public virtual Role? BaseRoleModel { get; set; }
|
||||
public virtual ICollection<Role> InverseBaseRoleModel { get; set; } = [];
|
||||
public virtual ICollection<Permission> Permissions { get; set; } = new HashSet<Permission>();
|
||||
public virtual ICollection<User> Users { get; set; } = [];
|
||||
|
||||
public bool IsAdmin() => BaseRoleModel != null ? BaseRoleModel.IsAdmin() : Id == AdminRole.Id;
|
||||
public bool IsContentManager() => BaseRoleModel != null ? BaseRoleModel.IsContentManager() : Id == ContentManagerRole.Id;
|
||||
public bool IsUser() => BaseRoleModel != null ? BaseRoleModel.IsUser() : Id == UserRole.Id;
|
||||
|
||||
public bool HasPermission(Permission permission)
|
||||
{
|
||||
var baseRoleHasPermission = BaseRoleModel != null && BaseRoleModel.HasPermission(permission);
|
||||
return baseRoleHasPermission || Permissions.Any(p => p.Id == permission.Id);
|
||||
}
|
||||
|
||||
public Role() { }
|
||||
|
||||
public Role(string id, string name, string description, List<Permission>? permissions = null, Role? baseRoleModel = null)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Description = description;
|
||||
Permissions = permissions ?? [];
|
||||
if (baseRoleModel != null)
|
||||
{
|
||||
BaseRoleModel = baseRoleModel;
|
||||
BaseRoleModelId = baseRoleModel.Id;
|
||||
foreach (var permission in baseRoleModel.Permissions)
|
||||
{
|
||||
if (!Permissions.Any(p => p.Id == permission.Id))
|
||||
{
|
||||
Permissions.Add(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Name);
|
||||
|
||||
public override bool Equals(object? obj) => obj is Role otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(Role? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
|
||||
public bool IsNull => this is null;
|
||||
|
||||
public object Clone() => (Role)MemberwiseClone();
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
if (obj is not Role other) throw new ArgumentException("Object is not a Person");
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(Role? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public RoleDto ToDto()
|
||||
{
|
||||
return new RoleDto
|
||||
{
|
||||
Id = Id,
|
||||
Permissions = [.. Permissions.Select(p => p.ToDto())]
|
||||
};
|
||||
}
|
||||
|
||||
public static readonly Role UserRole = new(
|
||||
"1", "User", "Role for regular users",
|
||||
[
|
||||
Permission.ViewContentPermission,
|
||||
Permission.LikeContentPermission
|
||||
]
|
||||
);
|
||||
|
||||
public static readonly Role ContentManagerRole = new(
|
||||
"2", "Content Manager", "Role for managing content",
|
||||
[
|
||||
Permission.CreateContentPermission,
|
||||
Permission.EditContentPermission,
|
||||
Permission.DeleteContentPermission
|
||||
],
|
||||
UserRole
|
||||
);
|
||||
|
||||
public static readonly Role AdminRole = new(
|
||||
"3", "Admin", "Administrator role with full permissions",
|
||||
[
|
||||
Permission.CreateUserPermission,
|
||||
Permission.DisableUserPermission,
|
||||
Permission.EditUserPermission,
|
||||
Permission.DeleteUserPermission,
|
||||
Permission.EditWebConfigPermission
|
||||
],
|
||||
ContentManagerRole
|
||||
);
|
||||
}
|
33
back/DataModels/SocialMedia.cs
Normal file
33
back/DataModels/SocialMedia.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("SocialMedia")]
|
||||
public partial class SocialMedia: IEquatable<SocialMedia>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
public string? Facebook { get; set; }
|
||||
public string? Instagram { get; set; }
|
||||
public string? Twitter { get; set; }
|
||||
public string? BlueSky { get; set; }
|
||||
public string? Tiktok { get; set; }
|
||||
public string? Linkedin { get; set; }
|
||||
public string? Pinterest { get; set; }
|
||||
public string? Discord { get; set; }
|
||||
public string? Reddit { get; set; }
|
||||
public string? Other { get; set; }
|
||||
public virtual ICollection<Person> People { get; set; } = [];
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id);
|
||||
|
||||
public override bool Equals(object? obj) => obj is SocialMedia otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(SocialMedia? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
}
|
15
back/DataModels/SystemKey.cs
Normal file
15
back/DataModels/SystemKey.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
}
|
32
back/DataModels/Tag.cs
Normal file
32
back/DataModels/Tag.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Tags")]
|
||||
public partial class Tag: IEquatable<Tag>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, MaxLength(25)]
|
||||
public string Name { get; set; } = null!;
|
||||
[Required]
|
||||
public string CreatedAt { get; set; } = null!;
|
||||
|
||||
public virtual ICollection<Event> Events { get; set; } = [];
|
||||
|
||||
public virtual ICollection<Gallery> Galleries { get; set; } = [];
|
||||
|
||||
public virtual ICollection<Photo> Photos { get; set; } = [];
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Name);
|
||||
|
||||
public override bool Equals(object? obj) => obj is Tag tag && Equals(tag);
|
||||
|
||||
public bool Equals(Tag? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id || GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
}
|
86
back/DataModels/User.cs
Normal file
86
back/DataModels/User.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using back.DTO;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.DataModels;
|
||||
|
||||
[Table("Users")]
|
||||
public class User : IEntity<User>
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; } = null!;
|
||||
[Required, EmailAddress]
|
||||
public string Email { get; set; } = null!;
|
||||
[Required, MinLength(8)]
|
||||
public string Password { get; set; } = null!;
|
||||
[Required]
|
||||
public string Salt { get; set; } = null!;
|
||||
public string CreatedAt { get; set; } = null!;
|
||||
|
||||
public virtual Person IdNavigation { get; set; } = null!;
|
||||
public virtual ICollection<Gallery> Galleries { get; set; } = [];
|
||||
public virtual ICollection<Gallery> GalleriesNavigation { get; set; } = [];
|
||||
public virtual ICollection<Photo> Photos { get; set; } = [];
|
||||
public virtual ICollection<Role> Roles { get; set; } = [];
|
||||
|
||||
public User() { }
|
||||
public User(string id, string email, string password, DateTimeOffset createdAt)
|
||||
{
|
||||
Id = id;
|
||||
Email = email;
|
||||
Password = password;
|
||||
CreatedAt = createdAt.ToString("dd-MM-yyyy HH:mm:ss zz");
|
||||
}
|
||||
|
||||
public UserDto ToDto() => new()
|
||||
{
|
||||
Id = Id,
|
||||
Roles = [.. Roles.Select(r => r.ToDto())]
|
||||
};
|
||||
|
||||
public bool IsAdmin() => Roles.Any(r => r.IsAdmin());
|
||||
public bool IsContentManager() => Roles.Any(r => r.IsContentManager());
|
||||
public bool IsUser() => Roles.Any(r => r.IsUser());
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, Email);
|
||||
|
||||
public override bool Equals(object? obj) => obj is User otherEvent && Equals(otherEvent);
|
||||
|
||||
public bool Equals(User? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id && Email == other.Email;
|
||||
}
|
||||
|
||||
public bool IsNull => this is null;
|
||||
|
||||
public object Clone() => (User)MemberwiseClone();
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
if (obj is not User other) throw new ArgumentException("Object is not a Person");
|
||||
return CompareTo(other);
|
||||
}
|
||||
|
||||
public int CompareTo(User? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public const string SystemUserId = "00000000-0000-0000-0000-000000000001";
|
||||
|
||||
public static readonly User SystemUser = new(
|
||||
id: SystemUserId,
|
||||
email: "sys@t.em",
|
||||
password: "",
|
||||
createdAt: DateTime.UtcNow
|
||||
)
|
||||
{
|
||||
Roles = [Role.AdminRole, Role.ContentManagerRole, Role.UserRole]
|
||||
};
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.Domain;
|
||||
|
||||
public class ApplicationDbContext : DbContext
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
Database.Migrate();
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace back.Domain;
|
||||
|
||||
public interface IEntity
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
string Id
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
using AutoMapper;
|
||||
|
||||
namespace back.Infrastructure;
|
||||
|
||||
public class AutoMapperProfile : Profile
|
||||
{
|
||||
public AutoMapperProfile()
|
||||
{
|
||||
//CreateMap<Users, User>()
|
||||
// .ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId))
|
||||
// .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
|
||||
// .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName))
|
||||
// .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
|
||||
// .ForMember(dest => dest.BirthYear, opt => opt.MapFrom(src => src.Birthday.Year))
|
||||
// .ForMember(dest => dest.BirthMonth, opt => opt.MapFrom(src => src.Birthday.Month))
|
||||
// .ForMember(dest => dest.BirthDay, opt => opt.MapFrom(src => src.Birthday.Day))
|
||||
// .ForMember(dest => dest.OccupationName, opt => opt.Ignore())
|
||||
|
||||
}
|
||||
}
|
14
back/Options/DatabaseConfig.cs
Normal file
14
back/Options/DatabaseConfig.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
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; }
|
||||
}
|
6
back/Options/Databases.cs
Normal file
6
back/Options/Databases.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace back.Options;
|
||||
|
||||
public sealed class Databases
|
||||
{
|
||||
public string? BaseDirectory { get; set; }
|
||||
}
|
10
back/Options/MailServerOptions.cs
Normal file
10
back/Options/MailServerOptions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace back.Options;
|
||||
|
||||
public sealed class MailServerOptions
|
||||
{
|
||||
public required string SmtpServer { get; set; }
|
||||
public required int Puerto { get; set; }
|
||||
public required string Usuario { get; set; }
|
||||
public required string Password { get; set; }
|
||||
public bool EnableSsl { get; set; }
|
||||
}
|
160
back/Program.cs
160
back/Program.cs
@@ -1,150 +1,48 @@
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using AutoMapper;
|
||||
using back.Domain;
|
||||
using back.Infrastructure;
|
||||
using back.ServicesExtensions;
|
||||
using MCVIngenieros.Healthchecks;
|
||||
using MediatR.Extensions.FluentValidation.AspNetCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
using Scalar.AspNetCore;
|
||||
using Serilog;
|
||||
|
||||
namespace back;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var configFiles = Path.Combine(AppContext.BaseDirectory, "configs");
|
||||
if (!Directory.Exists(configFiles))
|
||||
{
|
||||
Directory.CreateDirectory(configFiles);
|
||||
}
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
|
||||
builder.Services.UseExtensions();
|
||||
|
||||
var configurationBuilder = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{environment}.json", optional: false, reloadOnChange: true);
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var configs = Directory.GetFiles(configFiles, "*.json", SearchOption.AllDirectories);
|
||||
foreach (var config in configs)
|
||||
{
|
||||
configurationBuilder.AddJsonFile(config, optional: true, reloadOnChange: true);
|
||||
}
|
||||
builder.Services.AddHealthChecksSupport().DiscoverHealthChecks();
|
||||
|
||||
var configuration = configurationBuilder.Build();
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.CreateLogger();
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll",
|
||||
builder => builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader());
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.AddConfiguration(configuration);
|
||||
builder.Host.UseSerilog();
|
||||
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
|
||||
var app = builder.Build();
|
||||
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
{
|
||||
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
|
||||
});
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
builder.Services.AddFluentValidation([typeof(Program).Assembly]);
|
||||
builder.Services.AddAutoMapper(opts =>
|
||||
{
|
||||
opts.AddProfile<AutoMapperProfile>();
|
||||
opts.AllowNullCollections = true;
|
||||
opts.AllowNullDestinationValues = true;
|
||||
opts.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
|
||||
});
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
builder.Services.AddHealthChecksSupport().DiscoverHealthChecks();
|
||||
builder.Services.AddLogging();
|
||||
builder.Logging.AddOpenTelemetry(options =>
|
||||
{
|
||||
options
|
||||
.SetResourceBuilder(
|
||||
ResourceBuilder.CreateDefault()
|
||||
.AddService(AppDomain.CurrentDomain.FriendlyName))
|
||||
.AddConsoleExporter();
|
||||
});
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.ConfigureResource(resource => resource.AddService(AppDomain.CurrentDomain.FriendlyName))
|
||||
.WithTracing(tracing => tracing
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddConsoleExporter())
|
||||
.WithMetrics(metrics => metrics
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddConsoleExporter());
|
||||
app.UseAuthorization();
|
||||
|
||||
builder.Services.AddDataProtection();
|
||||
app.UseCors("AllowAll");
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy
|
||||
.WithOrigins(builder.Configuration.GetSection("AllowedHosts").Get<string[]>() ?? [])
|
||||
.SetIsOriginAllowedToAllowWildcardSubdomains()
|
||||
.SetPreflightMaxAge(TimeSpan.FromMinutes(10))
|
||||
.AllowCredentials()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddAntiforgery(options =>
|
||||
{
|
||||
options.HeaderName = "X-XSRF-TOKEN";
|
||||
});
|
||||
app.MapControllers();
|
||||
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(opts =>
|
||||
{
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
opts.UseSqlite(connectionString);
|
||||
});
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add(new Microsoft.AspNetCore.Mvc.RequireHttpsAttribute());
|
||||
});
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
app.MapScalarApiReference("/api-docs", opt =>
|
||||
{
|
||||
opt.WithTitle("My API Documentation");
|
||||
});
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Application start-up failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,21 @@
|
||||
{
|
||||
"$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": "api-docs",
|
||||
"applicationUrl": "https://localhost:7157",
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7273;http://localhost:5250",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
49
back/ServicesExtensions/DatabaseContexts.cs
Normal file
49
back/ServicesExtensions/DatabaseContexts.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using back.Options;
|
||||
using back.persistance.data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace back.ServicesExtensions;
|
||||
|
||||
public static partial class ServicesExtensions
|
||||
{
|
||||
private static IServiceCollection AddDatabaseContext(this IServiceCollection services)
|
||||
{
|
||||
services.AddContext<DataContext>();
|
||||
|
||||
return services;
|
||||
}
|
||||
private static IServiceCollection AddContext<T>(this IServiceCollection services)
|
||||
where T : DbContext
|
||||
{
|
||||
var config = services
|
||||
.BuildServiceProvider()
|
||||
.GetRequiredService<IOptionsSnapshot<DatabaseConfig>>()
|
||||
.Get(DatabaseConfig.DataStorage);
|
||||
|
||||
services.AddDbContext<T>(options =>
|
||||
{
|
||||
options.UseDatabaseConfig(config);
|
||||
});
|
||||
|
||||
using var scope = services.BuildServiceProvider().CreateScope();
|
||||
var context = scope.ServiceProvider
|
||||
.GetRequiredService<T>();
|
||||
var isDevelopment = scope.ServiceProvider
|
||||
.GetRequiredService<IHostEnvironment>()
|
||||
.IsDevelopment();
|
||||
|
||||
if (isDevelopment && !context.Database.HasPendingModelChanges())
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
context.Database.Migrate();
|
||||
}
|
||||
context.SaveChanges();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
92
back/ServicesExtensions/DdContextOptionsBuilderExtensions.cs
Normal file
92
back/ServicesExtensions/DdContextOptionsBuilderExtensions.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using back.Options;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace back.ServicesExtensions;
|
||||
|
||||
public enum DatabaseProvider
|
||||
{
|
||||
/* -- Relational databases supported by EF Core -- */
|
||||
SUPPORTED, // Placeholder for supported databases.
|
||||
InMemory,
|
||||
Sqlite,
|
||||
PostgreSQL,
|
||||
CockroachDB, // CockroachDB is compatible with PostgreSQL.
|
||||
SQLServer,
|
||||
MariaDB,
|
||||
MySQL,
|
||||
Oracle, // Oracle is supported by EF Core but requires a separate package.
|
||||
|
||||
/* -- NoSQL are not supported by EF -- */
|
||||
|
||||
NOT_SUPPORTED, // Placeholder for unsupported databases.
|
||||
Firebird, // Firebird is supported by EF Core but requires a separate package.
|
||||
Db2, // Db2 is supported by EF Core but requires a separate package.
|
||||
SAPHana, // SAP HANA is supported by EF Core but requires a separate package.
|
||||
Sybase, // Sybase is supported by EF Core but requires a separate package.
|
||||
Cosmos, // Cosmos DB is database supported by EF Core.
|
||||
MongoDB,
|
||||
InfluxDB,
|
||||
Redis,
|
||||
Cassandra,
|
||||
ElasticSearch,
|
||||
CouchDB,
|
||||
RavenDB,
|
||||
Neo4j,
|
||||
OrientDB,
|
||||
ArangoDB,
|
||||
ClickHouse,
|
||||
Druid,
|
||||
TimescaleDB,
|
||||
}
|
||||
|
||||
public static partial class DbContextOptionsBuilderExtensions
|
||||
{
|
||||
private static string SupportedDbs()
|
||||
=> string.Join(", ", Enum.GetValues<DatabaseProvider>()
|
||||
.Where(db => db > DatabaseProvider.SUPPORTED && db < DatabaseProvider.NOT_SUPPORTED)
|
||||
.OrderBy(db => db)
|
||||
.Select(db => db.ToString()));
|
||||
|
||||
public static void UseDatabaseConfig(this DbContextOptionsBuilder options, DatabaseConfig config)
|
||||
{
|
||||
var providerName = Enum.GetNames<DatabaseProvider>()
|
||||
.FirstOrDefault(name => name.Equals(config.Provider, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (!Enum.TryParse(providerName, out DatabaseProvider provider))
|
||||
{
|
||||
throw new InvalidOperationException($"Unsupported database provider: {config.Provider} -- Supported providers are: {SupportedDbs()}");
|
||||
}
|
||||
|
||||
switch (provider)
|
||||
{
|
||||
case DatabaseProvider.Sqlite:
|
||||
var match = SQLiteRegex().Match(config.ConnectionString ?? string.Empty);
|
||||
if (match.Success)
|
||||
{
|
||||
string? folder = null;
|
||||
string path = match.Groups[1].Value.Replace("\\", "/");
|
||||
folder = path.Contains('/') ? path[..path.IndexOf('/')] : path;
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
options.UseSqlite(config.ConnectionString);
|
||||
break;
|
||||
case DatabaseProvider.InMemory:
|
||||
options.UseInMemoryDatabase(config.ConnectionString);
|
||||
break;
|
||||
case DatabaseProvider.PostgreSQL or DatabaseProvider.CockroachDB:
|
||||
options.UseNpgsql(config.ConnectionString);
|
||||
break;
|
||||
case DatabaseProvider.SQLServer:
|
||||
options.UseSqlServer(config.ConnectionString);
|
||||
break;
|
||||
case DatabaseProvider.MySQL or DatabaseProvider.MariaDB:
|
||||
options.UseMySql(config.ConnectionString, ServerVersion.AutoDetect(config.ConnectionString));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported database provider: {config.Provider}");
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"Data Source=([^;]+)")]
|
||||
private static partial Regex SQLiteRegex();
|
||||
}
|
76
back/ServicesExtensions/Options.cs
Normal file
76
back/ServicesExtensions/Options.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using back.healthchecks.Options;
|
||||
using back.Options;
|
||||
|
||||
namespace back.ServicesExtensions;
|
||||
|
||||
public static partial class ServicesExtensions
|
||||
{
|
||||
private static IConfiguration ConfigureOptions(this IServiceCollection services)
|
||||
{
|
||||
IConfiguration config = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
|
||||
string? baseDirectory = null;
|
||||
|
||||
services.Configure<Databases>(config.GetSection(nameof(Databases)));
|
||||
services.Configure<DatabaseConfig>(DatabaseConfig.DataStorage, config.GetSection(DatabaseConfig.DataStorage));
|
||||
services.Configure<DatabaseConfig>(DatabaseConfig.BlobStorage, config.GetSection(DatabaseConfig.BlobStorage));
|
||||
services.Configure<MailServerOptions>(config.GetSection(nameof(MailServerOptions)));
|
||||
|
||||
services.Configure<HealthChecksConfigs>(HealthChecksConfigs.Sqlite, config.GetSection(HealthChecksConfigs.Sqlite));
|
||||
|
||||
services.PostConfigure<Databases>(databases =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(databases.BaseDirectory) && !Directory.Exists(databases.BaseDirectory))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(databases.BaseDirectory);
|
||||
Console.WriteLine($"Base directory created at: {databases.BaseDirectory}");
|
||||
baseDirectory = databases.BaseDirectory;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to create base directory at {databases.BaseDirectory}. " +
|
||||
"Please ensure the path is valid and accessible.", ex
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
services.PostConfigure<DatabaseConfig>(DatabaseConfig.DataStorage, config =>
|
||||
{
|
||||
PostConfigureDatabaseConfig(config, baseDirectory);
|
||||
});
|
||||
|
||||
services.PostConfigure<DatabaseConfig>(DatabaseConfig.BlobStorage, config =>
|
||||
{
|
||||
PostConfigureDatabaseConfig(config, baseDirectory);
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static void PostConfigureDatabaseConfig(DatabaseConfig config, string? baseDirectory)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(config.SystemContainer))
|
||||
{
|
||||
var path = config.SystemContainer;
|
||||
if (!string.IsNullOrEmpty(baseDirectory))
|
||||
{
|
||||
path = Path.Combine(baseDirectory, path);
|
||||
}
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
Console.WriteLine($"System container for {config.Provider} created at: {path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to create system container at {path}. " +
|
||||
"Please ensure the path is valid and accessible.", ex
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
back/ServicesExtensions/ServicesExtensions.cs
Normal file
49
back/ServicesExtensions/ServicesExtensions.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using back.persistance.data;
|
||||
using System.Text.Json.Serialization;
|
||||
using back.services.engine.SystemUser;
|
||||
using DependencyInjector;
|
||||
using System.Text.Json;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
|
||||
namespace back.ServicesExtensions;
|
||||
|
||||
public static partial class ServicesExtensions
|
||||
{
|
||||
public static IServiceCollection UseExtensions(this IServiceCollection services)
|
||||
{
|
||||
//var config =
|
||||
services.ConfigureOptions();
|
||||
|
||||
services.AddMemoryCache();
|
||||
|
||||
services.AddDatabaseContext();
|
||||
services.AddServices();
|
||||
|
||||
services.AddScoped<ITransactionalService<DataContext>, EntityFrameworkTransactionalService<DataContext>>();
|
||||
|
||||
services.AddSingleton(new JsonSerializerOptions {
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
Converters = {
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
|
||||
},
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
|
||||
UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement,
|
||||
});
|
||||
|
||||
using var scope = services.BuildServiceProvider().CreateScope();
|
||||
scope.ServiceProvider
|
||||
.GetRequiredService<ISystemUserGenerator>().GenerateAsync().Wait();
|
||||
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@@ -1,9 +1,34 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"MailServerOptions": {
|
||||
"SmtpServer": "smtp.gmail.com",
|
||||
"Puerto": 587,
|
||||
"Usuario": "",
|
||||
"Password": "",
|
||||
"EnableSsl": true
|
||||
},
|
||||
"HealthChecksConfigs": {
|
||||
"CacheDuration": "00:30:00",
|
||||
"Timeout": "00:00:05",
|
||||
"AssembliesToScan": [
|
||||
"back"
|
||||
],
|
||||
"Sqlite": {
|
||||
"RetryAttempts": 2,
|
||||
"Timeout": "00:05:00",
|
||||
"RetryDelay": "00:00:10",
|
||||
"Severity": "Info"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,5 +5,14 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "mmorales.photo"
|
||||
"Databases": {
|
||||
"Data": {
|
||||
"Provider": "sqlite",
|
||||
"ConnectionString": "Data Source=data/app.db;Cache=Shared"
|
||||
},
|
||||
"Blob": {
|
||||
"Provider": "system",
|
||||
"baseUrl": "https://back.mmorales.photo/api/photo/{id}/{res}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,9 @@
|
||||
{
|
||||
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
@@ -7,64 +7,48 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="8.4.0" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Autofac.WebApi2" Version="6.1.1" />
|
||||
<PackageReference Include="FluentValidation" Version="12.0.0" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.15.0" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
||||
<PackageReference Include="Mapster.EFCore" Version="5.1.1" />
|
||||
<PackageReference Include="MCVIngenieros.Healthchecks" Version="0.0.1" />
|
||||
<PackageReference Include="MediatR" Version="13.0.0" />
|
||||
<PackageReference Include="MediatR.Contracts" Version="2.0.1" />
|
||||
<PackageReference Include="MediatR.Extensions.Autofac.DependencyInjection" Version="13.1.0" />
|
||||
<PackageReference Include="MediatR.Extensions.FluentValidation.AspNetCore" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Extensions" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.UnitOfWork" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
<PackageReference Include="OpenTelemetry" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.7.2" />
|
||||
<PackageReference Include="Scalar.AspNetCore.Microsoft" Version="2.7.2" />
|
||||
<PackageReference Include="Scalar.AspNetCore.Swashbuckle" Version="2.7.2" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.4" />
|
||||
<PackageReference Include="Oracle.EntityFrameworkCore" Version="9.23.90" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Application\" />
|
||||
<Folder Include="Infrastructure\" />
|
||||
<Folder Include="Presentation\" />
|
||||
<ProjectReference Include="..\..\nuget\DependencyInjector\DependencyInjector.csproj" />
|
||||
<ProjectReference Include="..\..\nuget\Transactional\MCVIngenieros.Transactional.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,9 +1,15 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36401.2 d17.14
|
||||
VisualStudioVersion = 17.14.36401.2
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "back", "back.csproj", "{C78E8225-44D3-434B-AC2A-C8F4459BB18C}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "back", "back.csproj", "{392278F3-4B36-47F4-AD31-5FBFCC181AD4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCVIngenieros.Transactional", "..\..\nuget\Transactional\MCVIngenieros.Transactional.csproj", "{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjector", "..\..\nuget\DependencyInjector\DependencyInjector.csproj", "{DBDF84A4-235C-4F29-8626-5BD1DC255970}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presentation", "..\backend\Presentation\Presentation.csproj", "{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -11,15 +17,27 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{392278F3-4B36-47F4-AD31-5FBFCC181AD4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED76105A-5E6F-4997-86FE-6A7902A2AEBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DBDF84A4-235C-4F29-8626-5BD1DC255970}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1DD9D2A-0467-41EE-B3BB-303F1A0C18D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D5ABA005-3E91-4220-9B2C-874C0BED7E34}
|
||||
SolutionGuid = {F531A9C8-70D1-45AA-B4AA-AC49FCADAE3D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"HealthChecksConfigs": {
|
||||
"CacheDuration": "00:30:00",
|
||||
"Timeout": "00:00:05",
|
||||
"AssembliesToScan": [
|
||||
"back"
|
||||
]
|
||||
//"MyCheck": {
|
||||
// "RetryAttempts": 2,
|
||||
// "Timeout": "00:05:00",
|
||||
// "RetryDelay": "00:00:10",
|
||||
// "Severity": "Info"
|
||||
//}
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.OpenTelemetry" ],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information"
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "Logs/log-.txt",
|
||||
"rollingInterval": "Day",
|
||||
"fileSizeLimitBytes": 5242880,
|
||||
"rollOnFileSizeLimit": true,
|
||||
"retainedFileCountLimit": 31,
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "OpenTelemetry",
|
||||
"Args": {
|
||||
"endpoint": "http://localhost:4317",
|
||||
"protocol": "Grpc",
|
||||
"resourceAttributes": {
|
||||
"service.name": "back.mmorales.photo",
|
||||
"deployment.environment": "development"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": [
|
||||
"FromLogContext",
|
||||
"WithThreadId",
|
||||
"WithProcessId",
|
||||
"WithEnvironmentName"
|
||||
]
|
||||
}
|
||||
}
|
44
back/controllers/AuthController.cs
Normal file
44
back/controllers/AuthController.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace back.controllers;
|
||||
|
||||
public class ValidationErrors
|
||||
{
|
||||
public string? Field { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutionErrors
|
||||
{
|
||||
public Exception? Exception { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
public abstract class ResponseBase
|
||||
{
|
||||
public object? Data { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public int StatusCode { get; set; }
|
||||
public ValidationErrors[] ValidationErrors { get; set; }
|
||||
public ExecutionErrors[] ExecutionErrors { get; set; }
|
||||
}
|
||||
|
||||
public record LoginRequest(string Username, string Password);
|
||||
|
||||
[ApiController, Route("api/[controller]")]
|
||||
public class AuthController(IAuthService authService) : ControllerBase
|
||||
{
|
||||
|
||||
private readonly IAuthService _authService = authService;
|
||||
|
||||
[HttpPost, Route("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
|
||||
{
|
||||
// validar que el usuario y la contraseña sean correctos
|
||||
// obtener el token JWT encriptado
|
||||
// obtener el refresh token
|
||||
// devolver el token JWT y el refresh token en los headers de las respuestas
|
||||
// devolver datos del usuario en el body de la respuesta
|
||||
}
|
||||
}
|
21
back/controllers/CryptoController.cs
Normal file
21
back/controllers/CryptoController.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using back.services.engine.Crypto;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace back.controllers;
|
||||
|
||||
[ApiController, Route("api/[controller]")]
|
||||
public class CryptoController(ICryptoService cryptoService) : ControllerBase
|
||||
{
|
||||
[HttpGet("[action]")] public async Task<IActionResult> RSA([FromHeader(Name = "X-client-thumbprint")] string clientId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clientId))
|
||||
{
|
||||
return BadRequest("Client ID is required.");
|
||||
}
|
||||
var key = cryptoService.GetPublicCertificate(clientId);
|
||||
if (key == null)
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError, "Failed to generate RSA keys.");
|
||||
return Ok(new { PublicKey = key });
|
||||
}
|
||||
}
|
67
back/controllers/PhotosController.cs
Normal file
67
back/controllers/PhotosController.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using back.DataModels;
|
||||
using back.DTO;
|
||||
using back.services.bussines.PhotoService;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace back.controllers;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class PhotosController(IPhotoService photoService) : ControllerBase
|
||||
{
|
||||
private readonly IPhotoService _photoService = photoService;
|
||||
|
||||
// GET: api/<PhotoController>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<Photo>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
(int totalItems, IEnumerable<Photo>? pageData) = await _photoService.GetPage(page, pageSize);
|
||||
Response.Headers.Append("X-Total-Count", totalItems.ToString());
|
||||
return Ok(pageData);
|
||||
}
|
||||
|
||||
// GET api/<PhotoController>/5
|
||||
[HttpGet("{id}/{res}")]
|
||||
public async Task<IActionResult> Get(string id, string res)
|
||||
{
|
||||
(string? mediaType, byte[]? fileBytes) = await _photoService.GetBytes(id, res.ToLower());
|
||||
if(fileBytes == null)
|
||||
return NotFound();
|
||||
return File(fileBytes, mediaType ?? "image/jpeg");
|
||||
}
|
||||
|
||||
// POST api/<PhotoController>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromForm] PhotoFormModel form)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (form.Image == null || form.Image.Length == 0)
|
||||
return BadRequest("No image uploaded.");
|
||||
|
||||
await _photoService.Create(form);
|
||||
|
||||
return Created();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
//// PUT api/<PhotoController>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Put([FromBody] Photo photo)
|
||||
{
|
||||
await _photoService.Update(photo);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// DELETE api/<PhotoController>/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(string id)
|
||||
{
|
||||
await _photoService.Delete(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
90
back/controllers/UsersController.cs
Normal file
90
back/controllers/UsersController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using back.DataModels;
|
||||
using back.services.bussines;
|
||||
using back.services.bussines.UserService;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace back.controllers;
|
||||
|
||||
public record UserLoginFromModel(string Email, string Password, string? SystemKey);
|
||||
public record ForgotPasswordFromModel(string Email);
|
||||
public record RegisterFromModel(string Name, string Email, string Password);
|
||||
|
||||
[ApiController, Route("api/[controller]")]
|
||||
public class UsersController(IUserService user) : ControllerBase
|
||||
{
|
||||
private readonly IUserService _user = user;
|
||||
// GET: api/<UsersController>
|
||||
//[HttpGet]
|
||||
//public async Task<ActionResult<IEnumerable<UserModel>>> Get([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
//{
|
||||
// var users = await _userContext.GetPage(page, pageSize);
|
||||
// var totalItems = await _userContext.GetTotalItems();
|
||||
// Response.Headers.Append("X-Total-Count", totalItems.ToString());
|
||||
// return Ok(users);
|
||||
//}
|
||||
//// GET api/<UsersController>/5
|
||||
//[HttpGet("{id}")]
|
||||
//public async Task<IActionResult> Get(Guid id)
|
||||
//{
|
||||
// var user = await _userContext.GetById(id);
|
||||
// if (user == null)
|
||||
// return NotFound();
|
||||
// return Ok(user);
|
||||
//}
|
||||
|
||||
[HttpPost("[action]")]
|
||||
public async Task<IActionResult> Login(
|
||||
[FromHeader(Name = "X-client-thumbprint")] string clientId,
|
||||
[FromBody] UserLoginFromModel user
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
return BadRequest("Client ID cannot be null or empty");
|
||||
|
||||
if (user == null || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
|
||||
return BadRequest(Errors.BadRequest.Description);
|
||||
|
||||
if (user.Email.Equals(DataModels.User.SystemUser.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.SystemKey))
|
||||
return Unauthorized(Errors.Unauthorized.Description);
|
||||
var systemUser = await _user.ValidateSystemUser(user.Email, user.Password, user.SystemKey, clientId);
|
||||
if (systemUser == null)
|
||||
return Unauthorized(Errors.Unauthorized.Description);
|
||||
return Ok(systemUser.ToDto());
|
||||
}
|
||||
|
||||
var existingUser = await _user.Login(user.Email, user.Password, clientId);
|
||||
if (existingUser == null)
|
||||
return Unauthorized(Errors.Unauthorized.Description);
|
||||
return Ok(existingUser);
|
||||
}
|
||||
|
||||
[HttpPost("forgot-password")]
|
||||
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordFromModel user)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Email))
|
||||
return BadRequest("Email cannot be null or empty");
|
||||
await _user.SendResetPassword(user.Email);
|
||||
return Ok("If the email exists, a reset password link has been sent.");
|
||||
}
|
||||
|
||||
// POST api/<UsersController>
|
||||
[HttpPost("[action]")]
|
||||
public async Task<IActionResult> Register(
|
||||
[FromHeader(Name = "X-client-thumbprint")] string clientId,
|
||||
[FromBody] RegisterFromModel user)
|
||||
{
|
||||
if (user == null)
|
||||
return BadRequest("User cannot be null");
|
||||
try
|
||||
{
|
||||
var createdUser = await _user.Create(clientId, new User() { Email = user.Email, Password = user.Password });
|
||||
return Created();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex);
|
||||
}
|
||||
}
|
||||
}
|
6
back/healthchecks/options/HealthChecksConfigs.cs
Normal file
6
back/healthchecks/options/HealthChecksConfigs.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace back.healthchecks.Options;
|
||||
|
||||
public partial class HealthChecksConfigs : MCVIngenieros.Healthchecks.Options.HealthChecksConfigs
|
||||
{
|
||||
public const string Sqlite = "Sqlite";
|
||||
}
|
52
back/healthchecks/sqlite.cs
Normal file
52
back/healthchecks/sqlite.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using back.healthchecks.Options;
|
||||
using back.Options;
|
||||
using MCVIngenieros.Healthchecks;
|
||||
using MCVIngenieros.Healthchecks.Abstracts;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace back.healthchecks;
|
||||
|
||||
public class SqliteHealthCheck(IOptionsMonitor<DatabaseConfig> databaseConfig, IOptionsMonitor<HealthChecksConfigs> healthchecksConfig) : HealthCheck
|
||||
{
|
||||
private readonly DatabaseConfig databaseConfig = databaseConfig.Get(DatabaseConfig.DataStorage);
|
||||
private readonly HealthChecksConfigs hcConfig = healthchecksConfig.Get(HealthChecksConfigs.Sqlite);
|
||||
|
||||
public string Description => "Conecta con la base de datos SQLite y trata de hacer una consulta sobre la tabla Users.";
|
||||
public int? RetryAttempts => hcConfig.RetryAttempts ?? 2;
|
||||
public TimeSpan? Timeout => hcConfig.Timeout ?? TimeSpan.FromSeconds(5);
|
||||
public TimeSpan? RetryDelay => hcConfig.RetryDelay ?? TimeSpan.FromSeconds(1);
|
||||
public HealthCheckSeverity? Severity => hcConfig.Severity ?? HealthCheckSeverity.Critical;
|
||||
|
||||
public override Task<HealthCheckResult> CheckAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var isHealthy = false;
|
||||
var details = string.Empty;
|
||||
try
|
||||
{
|
||||
using var connection = new Microsoft.Data.Sqlite.SqliteConnection(databaseConfig.ConnectionString);
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = $"SELECT COUNT(1) FROM Users WHERE Id = '{DataModels.User.SystemUserId}';";
|
||||
var result = command.ExecuteScalar();
|
||||
if (result != null && Convert.ToInt32(result) == 1)
|
||||
{
|
||||
isHealthy = true;
|
||||
details = "Connection to SQLite database successful and SystemUser exists.";
|
||||
}
|
||||
else
|
||||
{
|
||||
details = "Connection to SQLite database successful but SystemUser does not exist.";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
details = $"Failed to connect to SQLite database: {ex.Message}";
|
||||
}
|
||||
|
||||
return Task.FromResult(new HealthCheckResult(isHealthy)
|
||||
{
|
||||
Details = details,
|
||||
Severity = isHealthy ? HealthCheckSeverity.Info : HealthCheckSeverity.Critical
|
||||
});
|
||||
}
|
||||
}
|
111
back/persistance/blob/FileSystemImageStorageService.cs
Normal file
111
back/persistance/blob/FileSystemImageStorageService.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using back.Options;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace back.persistance.blob;
|
||||
|
||||
public class FileSystemImageStorageService(
|
||||
IOptions<Databases> systemOptions,
|
||||
IOptionsMonitor<DatabaseConfig> options,
|
||||
IMemoryCache memoryCache
|
||||
) : IBlobStorageService
|
||||
{
|
||||
private readonly string RootPath = systemOptions.Value.BaseDirectory ?? "data";
|
||||
private readonly DatabaseConfig config = options.Get(DatabaseConfig.BlobStorage);
|
||||
private readonly IMemoryCache cache = memoryCache;
|
||||
|
||||
private string GetFullPath(string fileName)
|
||||
{
|
||||
// Ensure the directory exists
|
||||
var path = Path.Join(RootPath, config.SystemContainer, fileName);
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
if (directory != null && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public async Task Delete(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = GetFullPath(fileName);
|
||||
if (cache.TryGetValue(path, out Stream? cachedStream))
|
||||
{
|
||||
cachedStream?.Dispose(); // Dispose the cached stream if it exists
|
||||
cache.Remove(path); // Remove from cache
|
||||
}
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Error deleting file {fileName}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Stream?> GetStream(string fileName)
|
||||
{
|
||||
var path = GetFullPath(fileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
if (cache.TryGetValue(path, out Stream? cachedStream))
|
||||
{
|
||||
return cachedStream;
|
||||
}
|
||||
// open the file stream for multiple reads and cache it for performance
|
||||
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
cache.CreateEntry(path)
|
||||
.SetValue(fileStream)
|
||||
.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // Cache for 30 minutes
|
||||
|
||||
return fileStream;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<byte[]?> GetBytes(string fileName)
|
||||
{
|
||||
var stream = await GetStream(fileName);
|
||||
if (stream != null)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task Save(Stream blobStream, string fileName)
|
||||
{
|
||||
var path = GetFullPath(fileName);
|
||||
if (cache.TryGetValue(path, out Stream? _) || File.Exists(path))
|
||||
{
|
||||
throw new InvalidOperationException($"File {fileName} already exists. Use Update for updating file info.");
|
||||
}
|
||||
using var fileStream = new FileStream(path, options: new FileStreamOptions {
|
||||
Access = FileAccess.Write,
|
||||
BufferSize = 4096,
|
||||
Mode = FileMode.OpenOrCreate,
|
||||
Share = FileShare.Read,
|
||||
});
|
||||
blobStream.Seek(0, SeekOrigin.Begin);
|
||||
await blobStream.CopyToAsync(fileStream);
|
||||
blobStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public async Task Update(Stream blobStream, string fileName)
|
||||
{
|
||||
var path = GetFullPath(fileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
await Delete(fileName);
|
||||
}
|
||||
await Save(blobStream, fileName);
|
||||
}
|
||||
}
|
||||
|
13
back/persistance/blob/IBlobStorageService.cs
Normal file
13
back/persistance/blob/IBlobStorageService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.persistance.blob;
|
||||
|
||||
public interface IBlobStorageService : ISingleton
|
||||
{
|
||||
Task Save(Stream blobStream, string fileName);
|
||||
Task<Stream?> GetStream(string fileName);
|
||||
Task<byte[]?> GetBytes(string fileName);
|
||||
Task Delete(string fileName);
|
||||
Task Update(Stream blobStream, string fileName);
|
||||
}
|
||||
|
56
back/persistance/data/DataContext.cs
Normal file
56
back/persistance/data/DataContext.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.relations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data;
|
||||
|
||||
public partial class DataContext : DbContext
|
||||
{
|
||||
public DataContext() { }
|
||||
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
|
||||
|
||||
public virtual DbSet<EfmigrationsLock> EfmigrationsLocks { get; set; }
|
||||
|
||||
public virtual DbSet<Event> Events { get; set; }
|
||||
|
||||
public virtual DbSet<Gallery> Galleries { get; set; }
|
||||
|
||||
public virtual DbSet<Permission> Permissions { get; set; }
|
||||
|
||||
public virtual DbSet<Person> Persons { get; set; }
|
||||
|
||||
public virtual DbSet<Photo> Photos { get; set; }
|
||||
|
||||
public virtual DbSet<Ranking> Rankings { get; set; }
|
||||
|
||||
public virtual DbSet<Role> Roles { get; set; }
|
||||
|
||||
public virtual DbSet<SocialMedia> SocialMedia { get; set; }
|
||||
|
||||
public virtual DbSet<Tag> Tags { get; set; }
|
||||
|
||||
public virtual DbSet<User> Users { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<EfmigrationsLock>(entity =>
|
||||
{
|
||||
entity.ToTable("__EFMigrationsLock");
|
||||
|
||||
entity.Property(e => e.Id).ValueGeneratedNever();
|
||||
});
|
||||
|
||||
typeof(IRelationEstablisher).Assembly.GetExportedTypes()
|
||||
.Where(t => typeof(IRelationEstablisher).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
||||
.ToList()
|
||||
.ForEach(seederType =>
|
||||
{
|
||||
var relationEstablisher = (IRelationEstablisher?)Activator.CreateInstance(seederType);
|
||||
relationEstablisher?.EstablishRelation(modelBuilder);
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||
}
|
752
back/persistance/data/migrations/20250824120656_InitialSetup.Designer.cs
generated
Normal file
752
back/persistance/data/migrations/20250824120656_InitialSetup.Designer.cs
generated
Normal file
@@ -0,0 +1,752 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using back.persistance.data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace back.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20250824120656_InitialSetup")]
|
||||
partial class InitialSetup
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
|
||||
|
||||
modelBuilder.Entity("EventTag", b =>
|
||||
{
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("EventId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("EventTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryPhoto", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "PhotoId");
|
||||
|
||||
b.HasIndex("PhotoId");
|
||||
|
||||
b.ToTable("GalleryPhotos", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryTag", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("GalleryTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryUserViewer", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("GalleryUserViewers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoPerson", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PersonId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "PersonId");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("PhotoPersons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoTag", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("PhotoTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoUserBuyer", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PhotoUserBuyers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RolePermission", b =>
|
||||
{
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RoleId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("RolePermissions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserRole", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.EfmigrationsLock", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Timestamp")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("__EFMigrationsLock", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Event", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Events");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Gallery", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IsArchived")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("IsFavorite")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsPublic")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Galleries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Permission", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Bio")
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfilePicture")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SocialMediaId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SocialMediaId");
|
||||
|
||||
b.ToTable("Persons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Photo", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("HighResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IsArchived")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsFavorite")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsPublic")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<string>("LowResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MidResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RankingId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Photos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Ranking", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TotalVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UpVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Rankings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BaseRoleModelId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BaseRoleModelId");
|
||||
|
||||
b.ToTable("Roles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BlueSky")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Discord")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Facebook")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Instagram")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Linkedin")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Other")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Pinterest")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Reddit")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tiktok")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Twitter")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "Name" }, "IX_Tags_Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EventTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Event", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("EventId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryPhoto", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryUserViewer", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoPerson", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PersonId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoUserBuyer", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RolePermission", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Permission", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserRole", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Gallery", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.User", "CreatedByNavigation")
|
||||
.WithMany("Galleries")
|
||||
.HasForeignKey("CreatedBy")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Event", "Event")
|
||||
.WithMany("Galleries")
|
||||
.HasForeignKey("EventId");
|
||||
|
||||
b.Navigation("CreatedByNavigation");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.SocialMedia", "SocialMedia")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("SocialMediaId");
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Photo", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", "CreatedByNavigation")
|
||||
.WithMany("Photos")
|
||||
.HasForeignKey("CreatedBy")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Event", "Event")
|
||||
.WithMany("Photos")
|
||||
.HasForeignKey("EventId");
|
||||
|
||||
b.Navigation("CreatedByNavigation");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Role", "BaseRoleModel")
|
||||
.WithMany("InverseBaseRoleModel")
|
||||
.HasForeignKey("BaseRoleModelId");
|
||||
|
||||
b.Navigation("BaseRoleModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", "IdNavigation")
|
||||
.WithOne("User")
|
||||
.HasForeignKey("back.DataModels.User", "Id")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdNavigation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Event", b =>
|
||||
{
|
||||
b.Navigation("Galleries");
|
||||
|
||||
b.Navigation("Photos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.Navigation("Photos");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.Navigation("InverseBaseRoleModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
|
||||
{
|
||||
b.Navigation("People");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.Navigation("Galleries");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
583
back/persistance/data/migrations/20250824120656_InitialSetup.cs
Normal file
583
back/persistance/data/migrations/20250824120656_InitialSetup.cs
Normal file
@@ -0,0 +1,583 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace back.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialSetup : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "__EFMigrationsLock",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Timestamp = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK___EFMigrationsLock", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Events",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
Date = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Location = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UpdatedAt = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DeletedAt = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Events", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 255, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Permissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Rankings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TotalVotes = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UpVotes = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DownVotes = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Rankings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Roles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 250, nullable: true),
|
||||
BaseRoleModelId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Roles", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Roles_Roles_BaseRoleModelId",
|
||||
column: x => x.BaseRoleModelId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SocialMedia",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Facebook = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Instagram = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Twitter = table.Column<string>(type: "TEXT", nullable: true),
|
||||
BlueSky = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Tiktok = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Linkedin = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Pinterest = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Discord = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Reddit = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Other = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SocialMedia", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tags",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 25, nullable: false),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tags", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RolePermissions",
|
||||
columns: table => new
|
||||
{
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PermissionId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RolePermissions", x => new { x.RoleId, x.PermissionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Permissions_PermissionId",
|
||||
column: x => x.PermissionId,
|
||||
principalTable: "Permissions",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Persons",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
ProfilePicture = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Avatar = table.Column<string>(type: "TEXT", nullable: true),
|
||||
SocialMediaId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Bio = table.Column<string>(type: "TEXT", maxLength: 250, nullable: true),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DeletedAt = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Persons", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Persons_SocialMedia_SocialMediaId",
|
||||
column: x => x.SocialMediaId,
|
||||
principalTable: "SocialMedia",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EventTags",
|
||||
columns: table => new
|
||||
{
|
||||
EventId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TagId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EventTags", x => new { x.EventId, x.TagId });
|
||||
table.ForeignKey(
|
||||
name: "FK_EventTags_Events_EventId",
|
||||
column: x => x.EventId,
|
||||
principalTable: "Events",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_EventTags_Tags_TagId",
|
||||
column: x => x.TagId,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Photos",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
Extension = table.Column<string>(type: "TEXT", nullable: true),
|
||||
LowResUrl = table.Column<string>(type: "TEXT", nullable: true),
|
||||
MidResUrl = table.Column<string>(type: "TEXT", nullable: true),
|
||||
HighResUrl = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
|
||||
EventId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RankingId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IsFavorite = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
|
||||
IsPublic = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 1),
|
||||
IsArchived = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Photos", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Photos_Events_EventId",
|
||||
column: x => x.EventId,
|
||||
principalTable: "Events",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Photos_Persons_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "Persons",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Email = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Password = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Salt = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Users_Persons_Id",
|
||||
column: x => x.Id,
|
||||
principalTable: "Persons",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PhotoPersons",
|
||||
columns: table => new
|
||||
{
|
||||
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PersonId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PhotoPersons", x => new { x.PhotoId, x.PersonId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoPersons_Persons_PersonId",
|
||||
column: x => x.PersonId,
|
||||
principalTable: "Persons",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoPersons_Photos_PhotoId",
|
||||
column: x => x.PhotoId,
|
||||
principalTable: "Photos",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PhotoTags",
|
||||
columns: table => new
|
||||
{
|
||||
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TagId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PhotoTags", x => new { x.PhotoId, x.TagId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoTags_Photos_PhotoId",
|
||||
column: x => x.PhotoId,
|
||||
principalTable: "Photos",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoTags_Tags_TagId",
|
||||
column: x => x.TagId,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Galleries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
CreatedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UpdatedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
|
||||
IsPublic = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 1),
|
||||
IsArchived = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
|
||||
IsFavorite = table.Column<int>(type: "INTEGER", nullable: true, defaultValue: 0),
|
||||
IsDeleted = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DeletedAt = table.Column<string>(type: "TEXT", nullable: true),
|
||||
EventId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Galleries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Galleries_Events_EventId",
|
||||
column: x => x.EventId,
|
||||
principalTable: "Events",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Galleries_Users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PhotoUserBuyers",
|
||||
columns: table => new
|
||||
{
|
||||
PhotoId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PhotoUserBuyers", x => new { x.PhotoId, x.UserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoUserBuyers_Photos_PhotoId",
|
||||
column: x => x.PhotoId,
|
||||
principalTable: "Photos",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PhotoUserBuyers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRoles_Roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRoles_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GalleryPhotos",
|
||||
columns: table => new
|
||||
{
|
||||
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PhotoId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GalleryPhotos", x => new { x.GalleryId, x.PhotoId });
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryPhotos_Galleries_GalleryId",
|
||||
column: x => x.GalleryId,
|
||||
principalTable: "Galleries",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryPhotos_Photos_PhotoId",
|
||||
column: x => x.PhotoId,
|
||||
principalTable: "Photos",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GalleryTags",
|
||||
columns: table => new
|
||||
{
|
||||
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TagId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GalleryTags", x => new { x.GalleryId, x.TagId });
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryTags_Galleries_GalleryId",
|
||||
column: x => x.GalleryId,
|
||||
principalTable: "Galleries",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryTags_Tags_TagId",
|
||||
column: x => x.TagId,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GalleryUserViewers",
|
||||
columns: table => new
|
||||
{
|
||||
GalleryId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GalleryUserViewers", x => new { x.GalleryId, x.UserId });
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryUserViewers_Galleries_GalleryId",
|
||||
column: x => x.GalleryId,
|
||||
principalTable: "Galleries",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_GalleryUserViewers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EventTags_TagId",
|
||||
table: "EventTags",
|
||||
column: "TagId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Galleries_CreatedBy",
|
||||
table: "Galleries",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Galleries_EventId",
|
||||
table: "Galleries",
|
||||
column: "EventId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GalleryPhotos_PhotoId",
|
||||
table: "GalleryPhotos",
|
||||
column: "PhotoId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GalleryTags_TagId",
|
||||
table: "GalleryTags",
|
||||
column: "TagId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GalleryUserViewers_UserId",
|
||||
table: "GalleryUserViewers",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Persons_SocialMediaId",
|
||||
table: "Persons",
|
||||
column: "SocialMediaId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PhotoPersons_PersonId",
|
||||
table: "PhotoPersons",
|
||||
column: "PersonId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Photos_CreatedBy",
|
||||
table: "Photos",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Photos_EventId",
|
||||
table: "Photos",
|
||||
column: "EventId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PhotoTags_TagId",
|
||||
table: "PhotoTags",
|
||||
column: "TagId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PhotoUserBuyers_UserId",
|
||||
table: "PhotoUserBuyers",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RolePermissions_PermissionId",
|
||||
table: "RolePermissions",
|
||||
column: "PermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Roles_BaseRoleModelId",
|
||||
table: "Roles",
|
||||
column: "BaseRoleModelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tags_Name",
|
||||
table: "Tags",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserRoles_RoleId",
|
||||
table: "UserRoles",
|
||||
column: "RoleId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "__EFMigrationsLock");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "EventTags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GalleryPhotos");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GalleryTags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GalleryUserViewers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PhotoPersons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PhotoTags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PhotoUserBuyers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Rankings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RolePermissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Galleries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Photos");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Permissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Roles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Events");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Persons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SocialMedia");
|
||||
}
|
||||
}
|
||||
}
|
749
back/persistance/data/migrations/DataContextModelSnapshot.cs
Normal file
749
back/persistance/data/migrations/DataContextModelSnapshot.cs
Normal file
@@ -0,0 +1,749 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using back.persistance.data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace back.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
partial class DataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
|
||||
|
||||
modelBuilder.Entity("EventTag", b =>
|
||||
{
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("EventId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("EventTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryPhoto", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "PhotoId");
|
||||
|
||||
b.HasIndex("PhotoId");
|
||||
|
||||
b.ToTable("GalleryPhotos", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryTag", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("GalleryTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryUserViewer", b =>
|
||||
{
|
||||
b.Property<string>("GalleryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GalleryId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("GalleryUserViewers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoPerson", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PersonId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "PersonId");
|
||||
|
||||
b.HasIndex("PersonId");
|
||||
|
||||
b.ToTable("PhotoPersons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoTag", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("PhotoTags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoUserBuyer", b =>
|
||||
{
|
||||
b.Property<string>("PhotoId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("PhotoId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PhotoUserBuyers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RolePermission", b =>
|
||||
{
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RoleId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("RolePermissions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserRole", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.EfmigrationsLock", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Timestamp")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("__EFMigrationsLock", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Event", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Events");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Gallery", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IsArchived")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("IsFavorite")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsPublic")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Galleries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Permission", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Bio")
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("IsDeleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfilePicture")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SocialMediaId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SocialMediaId");
|
||||
|
||||
b.ToTable("Persons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Photo", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("HighResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IsArchived")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsFavorite")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int?>("IsPublic")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<string>("LowResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MidResUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RankingId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UpdatedBy")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.ToTable("Photos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Ranking", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TotalVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UpVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Rankings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BaseRoleModelId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BaseRoleModelId");
|
||||
|
||||
b.ToTable("Roles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BlueSky")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Discord")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Facebook")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Instagram")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Linkedin")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Other")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Pinterest")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Reddit")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tiktok")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Twitter")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(25)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "Name" }, "IX_Tags_Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EventTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Event", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("EventId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryPhoto", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GalleryUserViewer", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Gallery", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoPerson", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PersonId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoTag", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Tag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhotoUserBuyer", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Photo", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RolePermission", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Permission", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UserRole", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Gallery", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.User", "CreatedByNavigation")
|
||||
.WithMany("Galleries")
|
||||
.HasForeignKey("CreatedBy")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Event", "Event")
|
||||
.WithMany("Galleries")
|
||||
.HasForeignKey("EventId");
|
||||
|
||||
b.Navigation("CreatedByNavigation");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.SocialMedia", "SocialMedia")
|
||||
.WithMany("People")
|
||||
.HasForeignKey("SocialMediaId");
|
||||
|
||||
b.Navigation("SocialMedia");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Photo", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", "CreatedByNavigation")
|
||||
.WithMany("Photos")
|
||||
.HasForeignKey("CreatedBy")
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("back.DataModels.Event", "Event")
|
||||
.WithMany("Photos")
|
||||
.HasForeignKey("EventId");
|
||||
|
||||
b.Navigation("CreatedByNavigation");
|
||||
|
||||
b.Navigation("Event");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Role", "BaseRoleModel")
|
||||
.WithMany("InverseBaseRoleModel")
|
||||
.HasForeignKey("BaseRoleModelId");
|
||||
|
||||
b.Navigation("BaseRoleModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.HasOne("back.DataModels.Person", "IdNavigation")
|
||||
.WithOne("User")
|
||||
.HasForeignKey("back.DataModels.User", "Id")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdNavigation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Event", b =>
|
||||
{
|
||||
b.Navigation("Galleries");
|
||||
|
||||
b.Navigation("Photos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Person", b =>
|
||||
{
|
||||
b.Navigation("Photos");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.Role", b =>
|
||||
{
|
||||
b.Navigation("InverseBaseRoleModel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.SocialMedia", b =>
|
||||
{
|
||||
b.Navigation("People");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("back.DataModels.User", b =>
|
||||
{
|
||||
b.Navigation("Galleries");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
200
back/persistance/data/migrations/sqlite/tables.sql
Normal file
200
back/persistance/data/migrations/sqlite/tables.sql
Normal file
@@ -0,0 +1,200 @@
|
||||
-- Tabla de redes sociales (SocialMedia) y relación uno a uno con Person
|
||||
CREATE TABLE IF NOT EXISTS SocialMedia (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Facebook TEXT,
|
||||
Instagram TEXT,
|
||||
Twitter TEXT,
|
||||
BlueSky TEXT,
|
||||
Tiktok TEXT,
|
||||
Linkedin TEXT,
|
||||
Pinterest TEXT,
|
||||
Discord TEXT,
|
||||
Reddit TEXT,
|
||||
Other TEXT
|
||||
);
|
||||
|
||||
-- Person: cada persona tiene un grupo de redes sociales (uno a uno, fk opcional)
|
||||
CREATE TABLE IF NOT EXISTS Persons (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Name TEXT NOT NULL,
|
||||
ProfilePicture TEXT,
|
||||
Avatar TEXT,
|
||||
SocialMediaId TEXT,
|
||||
Bio TEXT,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
UpdatedAt TEXT,
|
||||
FOREIGN KEY (SocialMediaId) REFERENCES SocialMedia(Id)
|
||||
);
|
||||
|
||||
-- User: es una persona (herencia por clave primaria compartida)
|
||||
CREATE TABLE IF NOT EXISTS Users (
|
||||
Id TEXT PRIMARY KEY, -- MISMA clave y valor que Persons.Id
|
||||
Email TEXT NOT NULL,
|
||||
Password TEXT NOT NULL,
|
||||
Salt TEXT NOT NULL,
|
||||
FOREIGN KEY (Id) REFERENCES Persons(Id)
|
||||
);
|
||||
|
||||
-- Un usuario puede ver muchas galerías (muchos-a-muchos: Galleries <-> Users)
|
||||
CREATE TABLE IF NOT EXISTS GalleryUserViewers (
|
||||
GalleryId TEXT NOT NULL,
|
||||
UserId TEXT NOT NULL,
|
||||
PRIMARY KEY (GalleryId, UserId),
|
||||
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
|
||||
FOREIGN KEY (UserId) REFERENCES Users(Id)
|
||||
);
|
||||
|
||||
-- Un usuario ha creado muchas galerías (uno a muchos)
|
||||
-- Una galería solo puede ser creada por un usuario
|
||||
CREATE TABLE IF NOT EXISTS Galleries (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Title TEXT,
|
||||
Description TEXT,
|
||||
CreatedAt TEXT,
|
||||
UpdatedAt TEXT,
|
||||
CreatedBy TEXT NOT NULL, -- FK a Users
|
||||
IsPublic INTEGER DEFAULT 1,
|
||||
IsArchived INTEGER DEFAULT 0,
|
||||
IsFavorite INTEGER DEFAULT 0,
|
||||
EventId TEXT, -- FK opcional a Events (una galería puede asociarse a un evento)
|
||||
FOREIGN KEY (CreatedBy) REFERENCES Users(Id),
|
||||
FOREIGN KEY (EventId) REFERENCES Events(Id)
|
||||
);
|
||||
|
||||
-- Galería-Photo: una galería contiene muchas imagenes, una imagen puede estar en muchas galerías (muchos-a-muchos)
|
||||
CREATE TABLE IF NOT EXISTS GalleryPhotos (
|
||||
GalleryId TEXT NOT NULL,
|
||||
PhotoId TEXT NOT NULL,
|
||||
PRIMARY KEY (GalleryId, PhotoId),
|
||||
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
|
||||
FOREIGN KEY (PhotoId) REFERENCES Photos(Id)
|
||||
);
|
||||
|
||||
-- Tabla de eventos
|
||||
CREATE TABLE IF NOT EXISTS Events (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Title TEXT NOT NULL,
|
||||
Description TEXT,
|
||||
Date TEXT,
|
||||
Location TEXT,
|
||||
CreatedAt TEXT NOT NULL,
|
||||
UpdatedAt TEXT NOT NULL,
|
||||
CreatedBy TEXT,
|
||||
UpdatedBy TEXT,
|
||||
IsDeleted INTEGER NOT NULL DEFAULT 0,
|
||||
DeletedAt TEXT
|
||||
);
|
||||
|
||||
-- Tabla de fotos
|
||||
CREATE TABLE IF NOT EXISTS Photos (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Title TEXT NOT NULL,
|
||||
Description TEXT,
|
||||
Extension TEXT,
|
||||
LowResUrl TEXT,
|
||||
MidResUrl TEXT,
|
||||
HighResUrl TEXT,
|
||||
CreatedAt TEXT,
|
||||
UpdatedAt TEXT,
|
||||
CreatedBy TEXT NOT NULL, -- Persona que subió la foto: FK a Persons
|
||||
UpdatedBy TEXT,
|
||||
EventId TEXT, -- Una photo solo puede tener un evento asociado (FK)
|
||||
RankingId TEXT,
|
||||
IsFavorite INTEGER DEFAULT 0,
|
||||
IsPublic INTEGER DEFAULT 1,
|
||||
IsArchived INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (CreatedBy) REFERENCES Persons(Id),
|
||||
FOREIGN KEY (EventId) REFERENCES Events(Id)
|
||||
);
|
||||
|
||||
-- Una persona puede salir en muchas fotos, y una foto puede tener muchas personas (muchos-a-muchos)
|
||||
CREATE TABLE IF NOT EXISTS PhotoPersons (
|
||||
PhotoId TEXT NOT NULL,
|
||||
PersonId TEXT NOT NULL,
|
||||
PRIMARY KEY (PhotoId, PersonId),
|
||||
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
|
||||
FOREIGN KEY (PersonId) REFERENCES Persons(Id)
|
||||
);
|
||||
|
||||
-- Un usuario puede comprar muchas fotos para verlas, y una foto puede haber sido comprada por muchos usuarios
|
||||
-- (solo necesario si IsPublic = 0)
|
||||
CREATE TABLE IF NOT EXISTS PhotoUserBuyers (
|
||||
PhotoId TEXT NOT NULL,
|
||||
UserId TEXT NOT NULL,
|
||||
PRIMARY KEY (PhotoId, UserId),
|
||||
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
|
||||
FOREIGN KEY (UserId) REFERENCES Users(Id)
|
||||
);
|
||||
|
||||
-- Tabla de tags (únicos)
|
||||
CREATE TABLE IF NOT EXISTS Tags (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Name TEXT NOT NULL UNIQUE,
|
||||
CreatedAt TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Una foto puede tener muchos tags (muchos-a-muchos)
|
||||
CREATE TABLE IF NOT EXISTS PhotoTags (
|
||||
PhotoId TEXT NOT NULL,
|
||||
TagId TEXT NOT NULL,
|
||||
PRIMARY KEY (PhotoId, TagId),
|
||||
FOREIGN KEY (PhotoId) REFERENCES Photos(Id),
|
||||
FOREIGN KEY (TagId) REFERENCES Tags(Id)
|
||||
);
|
||||
|
||||
-- Un evento puede tener muchos tags (muchos-a-muchos)
|
||||
CREATE TABLE IF NOT EXISTS EventTags (
|
||||
EventId TEXT NOT NULL,
|
||||
TagId TEXT NOT NULL,
|
||||
PRIMARY KEY (EventId, TagId),
|
||||
FOREIGN KEY (EventId) REFERENCES Events(Id),
|
||||
FOREIGN KEY (TagId) REFERENCES Tags(Id)
|
||||
);
|
||||
|
||||
-- Una galería puede tener muchos tags (muchos-a-muchos)
|
||||
CREATE TABLE IF NOT EXISTS GalleryTags (
|
||||
GalleryId TEXT NOT NULL,
|
||||
TagId TEXT NOT NULL,
|
||||
PRIMARY KEY (GalleryId, TagId),
|
||||
FOREIGN KEY (GalleryId) REFERENCES Galleries(Id),
|
||||
FOREIGN KEY (TagId) REFERENCES Tags(Id)
|
||||
);
|
||||
|
||||
-- Rankings (por si corresponde)
|
||||
CREATE TABLE IF NOT EXISTS Rankings (
|
||||
Id TEXT PRIMARY KEY,
|
||||
TotalVotes INTEGER NOT NULL,
|
||||
UpVotes INTEGER NOT NULL,
|
||||
DownVotes INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Permissions y Roles, tal y como en el mensaje anterior...
|
||||
CREATE TABLE IF NOT EXISTS Permissions (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Name TEXT NOT NULL,
|
||||
Description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Roles (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Name TEXT NOT NULL,
|
||||
Description TEXT,
|
||||
BaseRoleModelId TEXT,
|
||||
FOREIGN KEY (BaseRoleModelId) REFERENCES Roles(Id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS UserRoles (
|
||||
UserId TEXT NOT NULL,
|
||||
RoleId TEXT NOT NULL,
|
||||
PRIMARY KEY (UserId, RoleId),
|
||||
FOREIGN KEY (UserId) REFERENCES Users(Id),
|
||||
FOREIGN KEY (RoleId) REFERENCES Roles(Id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS RolePermissions (
|
||||
RoleId TEXT NOT NULL,
|
||||
PermissionId TEXT NOT NULL,
|
||||
PRIMARY KEY (RoleId, PermissionId),
|
||||
FOREIGN KEY (RoleId) REFERENCES Roles(Id),
|
||||
FOREIGN KEY (PermissionId) REFERENCES Permissions(Id)
|
||||
);
|
28
back/persistance/data/relations/EventRelationEstablisher.cs
Normal file
28
back/persistance/data/relations/EventRelationEstablisher.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class EventRelationEstablisher: IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Event>(entity =>
|
||||
{
|
||||
entity.HasMany(d => d.Tags).WithMany(p => p.Events)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"EventTag",
|
||||
r => r.HasOne<Tag>().WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Event>().WithMany()
|
||||
.HasForeignKey("EventId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("EventId", "TagId");
|
||||
j.ToTable("EventTags");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class GalleryRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Gallery>(entity =>
|
||||
{
|
||||
entity.Property(e => e.IsArchived).HasDefaultValue(0);
|
||||
entity.Property(e => e.IsFavorite).HasDefaultValue(0);
|
||||
entity.Property(e => e.IsPublic).HasDefaultValue(1);
|
||||
|
||||
entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.Galleries)
|
||||
.HasForeignKey(d => d.CreatedBy)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull);
|
||||
|
||||
entity.HasOne(d => d.Event).WithMany(p => p.Galleries).HasForeignKey(d => d.EventId);
|
||||
|
||||
entity.HasMany(d => d.Photos).WithMany(p => p.Galleries)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"GalleryPhoto",
|
||||
r => r.HasOne<Photo>().WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Gallery>().WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("GalleryId", "PhotoId");
|
||||
j.ToTable("GalleryPhotos");
|
||||
});
|
||||
|
||||
entity.HasMany(d => d.Tags).WithMany(p => p.Galleries)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"GalleryTag",
|
||||
r => r.HasOne<Tag>().WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Gallery>().WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("GalleryId", "TagId");
|
||||
j.ToTable("GalleryTags");
|
||||
});
|
||||
|
||||
entity.HasMany(d => d.Users).WithMany(p => p.GalleriesNavigation)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"GalleryUserViewer",
|
||||
r => r.HasOne<User>().WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Gallery>().WithMany()
|
||||
.HasForeignKey("GalleryId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("GalleryId", "UserId");
|
||||
j.ToTable("GalleryUserViewers");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
8
back/persistance/data/relations/IRelationEstablisher.cs
Normal file
8
back/persistance/data/relations/IRelationEstablisher.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public interface IRelationEstablisher
|
||||
{
|
||||
void EstablishRelation(ModelBuilder modelBuilder);
|
||||
}
|
68
back/persistance/data/relations/PersonRelationEstablisher.cs
Normal file
68
back/persistance/data/relations/PersonRelationEstablisher.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class PersonRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Photo>(entity =>
|
||||
{
|
||||
entity.Property(e => e.IsArchived).HasDefaultValue(0);
|
||||
entity.Property(e => e.IsFavorite).HasDefaultValue(0);
|
||||
entity.Property(e => e.IsPublic).HasDefaultValue(1);
|
||||
|
||||
entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.Photos)
|
||||
.HasForeignKey(d => d.CreatedBy)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull);
|
||||
|
||||
entity.HasOne(d => d.Event).WithMany(p => p.Photos).HasForeignKey(d => d.EventId);
|
||||
|
||||
entity.HasMany(d => d.People).WithMany(p => p.PhotosNavigation)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"PhotoPerson",
|
||||
r => r.HasOne<Person>().WithMany()
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Photo>().WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("PhotoId", "PersonId");
|
||||
j.ToTable("PhotoPersons");
|
||||
});
|
||||
|
||||
entity.HasMany(d => d.Tags).WithMany(p => p.Photos)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"PhotoTag",
|
||||
r => r.HasOne<Tag>().WithMany()
|
||||
.HasForeignKey("TagId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Photo>().WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("PhotoId", "TagId");
|
||||
j.ToTable("PhotoTags");
|
||||
});
|
||||
|
||||
entity.HasMany(d => d.Users).WithMany(p => p.Photos)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"PhotoUserBuyer",
|
||||
r => r.HasOne<User>().WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Photo>().WithMany()
|
||||
.HasForeignKey("PhotoId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("PhotoId", "UserId");
|
||||
j.ToTable("PhotoUserBuyers");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
307
back/persistance/data/relations/PhotoContext.cs
Normal file
307
back/persistance/data/relations/PhotoContext.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
//using back.DataModels;
|
||||
//using back.DTO;
|
||||
//using back.persistance.blob;
|
||||
//using back.services.ImageResizer;
|
||||
//using Microsoft.EntityFrameworkCore;
|
||||
//using Microsoft.Extensions.Hosting.Internal;
|
||||
|
||||
//namespace back.persistance.data.relations;
|
||||
|
||||
//public class PhotoContext : DbContext
|
||||
//{
|
||||
|
||||
// private readonly IImageResizer _Resizer;
|
||||
// private readonly IBlobStorageService _BlobStorage;
|
||||
|
||||
// private readonly TagContext _tagContext;
|
||||
// private readonly EventContext _eventContext;
|
||||
// private readonly PersonRelationEstablisher _personContext;
|
||||
|
||||
// public PhotoContext(DbContextOptions<PhotoContext> options, IHostEnvironment hostingEnvironment,
|
||||
// IImageResizer resizer,
|
||||
// IBlobStorageService blobStorage,
|
||||
// TagContext tags,
|
||||
// EventContext events,
|
||||
// PersonRelationEstablisher persons
|
||||
// ) : base(options)
|
||||
// {
|
||||
// _Resizer = resizer;
|
||||
// _BlobStorage = blobStorage;
|
||||
// _tagContext = tags;
|
||||
// _eventContext = events;
|
||||
// _personContext = persons;
|
||||
|
||||
// if (hostingEnvironment.IsDevelopment())
|
||||
// {
|
||||
// Database.EnsureCreated();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Database.Migrate();
|
||||
// }
|
||||
// }
|
||||
|
||||
// protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
// {
|
||||
// // Photo -> Tags (muchos-a-muchos)
|
||||
// modelBuilder.Entity<Photo>()
|
||||
// .HasMany(p => p.Tags)
|
||||
// .WithMany(t => t.Photos)
|
||||
// .UsingEntity(j => j.ToTable("PhotoTags"));
|
||||
|
||||
// // Photo -> Persons (muchos-a-muchos)
|
||||
// modelBuilder.Entity<Photo>()
|
||||
// .HasMany(p => p.PersonsIn)
|
||||
// .WithMany(per => per.Photos)
|
||||
// .UsingEntity(j => j.ToTable("PhotoPersons"));
|
||||
|
||||
// // Photo -> Event (muchos-a-uno)
|
||||
// modelBuilder.Entity<Photo>()
|
||||
// .HasOne(p => p.Event)
|
||||
// .WithMany() // Un evento puede tener múltiples fotos
|
||||
// .HasForeignKey(p => p.EventId);
|
||||
|
||||
// // Photo -> Ranking (uno-a-uno)
|
||||
// modelBuilder.Entity<Photo>()
|
||||
// .HasOne(p => p.Ranking)
|
||||
// .WithOne(r => r.Photo) // Un ranking está asociado a una sola foto
|
||||
// .HasForeignKey<Photo>(p => p.RankingId);
|
||||
|
||||
// base.OnModelCreating(modelBuilder);
|
||||
// }
|
||||
|
||||
// public async Task CreateNew(PhotoFormModel? form)
|
||||
// {
|
||||
// if (form == null) { return; }
|
||||
|
||||
// var photo = new Photo(
|
||||
// Guid.NewGuid().ToString(),
|
||||
// form.Title,
|
||||
// form.Description ?? string.Empty,
|
||||
// string.Empty, // LowResUrl will be set later
|
||||
// string.Empty, // MidResUrl will be set later
|
||||
// string.Empty, // HighResUrl will be set later
|
||||
// DateTime.UtcNow,
|
||||
// DateTime.UtcNow,
|
||||
// form.UserId,
|
||||
// form.UserId
|
||||
// )
|
||||
// {
|
||||
// IsPublic = form.IsPublic
|
||||
// };
|
||||
|
||||
// List<Task> tasks = [
|
||||
// SaveBlob(photo, form),
|
||||
// LinkTags(photo, form.Tags ?? [], form.UserId),
|
||||
// LinkEvent(photo, form.Evento ?? "", form.UserId),
|
||||
// LinkPersons(photo, form.People ?? [], form.UserId),
|
||||
// ];
|
||||
|
||||
// await Task.WhenAll(tasks);
|
||||
// await Photos.AddAsync(photo);
|
||||
// await SaveChangesAsync();
|
||||
// }
|
||||
|
||||
// private async Task LinkPersons(Photo photo, string[] personas, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (photo == null || personas == null || personas.Length == 0) return;
|
||||
// foreach (var personId in personas)
|
||||
// {
|
||||
// var person = await _personContext.GetById(personId);
|
||||
// if (person != null)
|
||||
// {
|
||||
// await LinkPersons(photo, person, updatedBy);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task LinkPersons(Photo photo, Person tag, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (tag == null) return;
|
||||
// // Ensure the tag exists
|
||||
// if (await _personContext.Exists(tag.Id))
|
||||
// {
|
||||
// photo.PersonsIn ??= [];
|
||||
// photo.PersonsIn.Add(tag);
|
||||
// photo.UpdatedAt = DateTime.UtcNow;
|
||||
// photo.UpdatedBy = updatedBy; // or use a more appropriate value
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task LinkTags(Photo photo, string[] tags, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (photo == null || tags == null || tags.Length == 0) return;
|
||||
// foreach (var tagId in tags)
|
||||
// {
|
||||
// var tag = await _tagContext.GetById(tagId);
|
||||
// if (tag != null)
|
||||
// {
|
||||
// await LinkTag(photo, tag, updatedBy);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task LinkTag(Photo photo, Tag tag, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (tag == null) return;
|
||||
// // Ensure the tag exists
|
||||
// if (await _tagContext.Exists(tag.Id))
|
||||
// {
|
||||
// photo.Tags.Add(tag);
|
||||
// photo.UpdatedAt = DateTime.UtcNow;
|
||||
// photo.UpdatedBy = updatedBy; // or use a more appropriate value
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task LinkEvent(Photo photo, string eventId, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(eventId)) return;
|
||||
// var evento = await _eventContext.GetById(eventId);
|
||||
// if (evento != null)
|
||||
// {
|
||||
// await LinkEvent(photo, evento, updatedBy);
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task LinkEvent(Photo photo, Event? evento, string updatedBy = "SYSTEM")
|
||||
// {
|
||||
// if (evento == null) return;
|
||||
// // Ensure the event exists
|
||||
// if (await _eventContext.Exists(evento.Id))
|
||||
// {
|
||||
// photo.Event = evento;
|
||||
// photo.UpdatedAt = DateTime.UtcNow;
|
||||
// photo.UpdatedBy = updatedBy;
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async Task SaveBlob(Photo photo, PhotoFormModel form)
|
||||
// {
|
||||
// if (form.Image != null && form.Image.Length > 0)
|
||||
// {
|
||||
// var lowRes = await _Resizer.ResizeImage(form.Image, 480);
|
||||
// var midRes = await _Resizer.ResizeImage(form.Image, 720);
|
||||
// // Upload images to blob storage
|
||||
// photo.Extension = form.Image.FileName.Split('.').Last();
|
||||
// photo.LowResUrl = $"low/{photo.Id}.webp";
|
||||
// photo.MidResUrl = $"mid/{photo.Id}.webp";
|
||||
// photo.HighResUrl = $"high/{photo.Id}.{photo.Extension}";
|
||||
// await _BlobStorage.SaveAsync(lowRes, photo.LowResUrl);
|
||||
// await _BlobStorage.SaveAsync(midRes, photo.MidResUrl);
|
||||
// await _BlobStorage.SaveAsync(form.Image.OpenReadStream(), photo.HighResUrl);
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task<Photo?> GetById(string id)
|
||||
// {
|
||||
// return await GetById(Guid.Parse(id));
|
||||
// }
|
||||
|
||||
// public async Task<Photo?> GetById(Guid id)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// return await Photos.FindAsync(id);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task<int> GetTotalItems()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// return await Photos.CountAsync();
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task<IEnumerable<Photo>?> GetPage(int page = 1, int pageSize = 20)
|
||||
// {
|
||||
// if (page < 1) page = 1;
|
||||
// if (pageSize < 1) pageSize = 20;
|
||||
// try
|
||||
// {
|
||||
// return await Photos
|
||||
// .OrderByDescending(p => p.CreatedAt)
|
||||
// .Skip((page - 1) * pageSize)
|
||||
// .Take(pageSize)
|
||||
// .ToListAsync();
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task<bool> Exists(Photo? photo)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (photo == null) return false;
|
||||
// if (string.IsNullOrEmpty(photo.Id)) return false;
|
||||
// return await Photos.AnyAsync(p => p.Id == photo.Id);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return false; // Handle exceptions gracefully
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task<bool> Exists(string id)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(id)) return false;
|
||||
// return await Photos.AnyAsync(p => p.Id == id);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// return false; // Handle exceptions gracefully
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task Delete(Photo photo)
|
||||
// {
|
||||
// if (photo == null) return;
|
||||
// if (await Exists(photo))
|
||||
// {
|
||||
// // Delete the photo from blob storage
|
||||
// if (!string.IsNullOrEmpty(photo.LowResUrl))
|
||||
// await _BlobStorage.DeleteAsync(photo.LowResUrl);
|
||||
// if (!string.IsNullOrEmpty(photo.MidResUrl))
|
||||
// await _BlobStorage.DeleteAsync(photo.MidResUrl);
|
||||
// if (!string.IsNullOrEmpty(photo.HighResUrl))
|
||||
// await _BlobStorage.DeleteAsync(photo.HighResUrl);
|
||||
// Photos.Remove(photo);
|
||||
// await SaveChangesAsync();
|
||||
// }
|
||||
// }
|
||||
|
||||
// public async Task Update(Photo photo)
|
||||
// {
|
||||
// if (photo == null) return;
|
||||
// if (await Exists(photo))
|
||||
// {
|
||||
// var evento = photo.Event;
|
||||
// photo.Event = null;
|
||||
// await LinkEvent(photo, evento, photo.UpdatedBy);
|
||||
|
||||
// var tags = photo.Tags.Select(t => t.Id);
|
||||
// photo.Tags.Clear();
|
||||
// await LinkTags(photo, [.. tags], photo.UpdatedBy);
|
||||
|
||||
// var persons = photo.PersonsIn?.Select(t => t.Id) ?? [];
|
||||
// photo.PersonsIn = null;
|
||||
// await LinkPersons(photo, [.. persons], photo.UpdatedBy);
|
||||
|
||||
// Photos.Update(photo);
|
||||
// await SaveChangesAsync();
|
||||
// }
|
||||
// }
|
||||
//}
|
15
back/persistance/data/relations/PhotoRelationEstablisher.cs
Normal file
15
back/persistance/data/relations/PhotoRelationEstablisher.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class PhotoRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Person>(entity =>
|
||||
{
|
||||
entity.HasOne(d => d.SocialMedia).WithMany(p => p.People).HasForeignKey(d => d.SocialMediaId);
|
||||
});
|
||||
}
|
||||
}
|
25
back/persistance/data/relations/RoleContext.cs
Normal file
25
back/persistance/data/relations/RoleContext.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
//using back.DataModels;
|
||||
//using Microsoft.EntityFrameworkCore;
|
||||
|
||||
//namespace back.persistance.data.relations;
|
||||
|
||||
|
||||
//public class RoleContext : DbContext
|
||||
//{
|
||||
// protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
// {
|
||||
// // Role -> Permissions (muchos-a-muchos)
|
||||
// modelBuilder.Entity<Role>()
|
||||
// .HasMany(r => r.Permissions)
|
||||
// .WithMany(p => p.Roles)
|
||||
// .UsingEntity(j => j.ToTable("RolePermissions"));
|
||||
|
||||
// // Role -> BaseRole (auto-referencial)
|
||||
// modelBuilder.Entity<Role>()
|
||||
// .HasOne(r => r.BaseRoleModel)
|
||||
// .WithMany() // Un rol base puede ser heredado por múltiples roles
|
||||
// .HasForeignKey(r => r.BaseRoleModelId);
|
||||
|
||||
// base.OnModelCreating(modelBuilder);
|
||||
// }
|
||||
//}
|
30
back/persistance/data/relations/RoleRelationEstablisher.cs
Normal file
30
back/persistance/data/relations/RoleRelationEstablisher.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class RoleRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Role>(entity =>
|
||||
{
|
||||
entity.HasOne(d => d.BaseRoleModel).WithMany(p => p.InverseBaseRoleModel).HasForeignKey(d => d.BaseRoleModelId);
|
||||
|
||||
entity.HasMany(d => d.Permissions).WithMany(p => p.Roles)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"RolePermission",
|
||||
r => r.HasOne<Permission>().WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<Role>().WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("RoleId", "PermissionId");
|
||||
j.ToTable("RolePermissions");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
40
back/persistance/data/relations/SeedingDbContext.cs
Normal file
40
back/persistance/data/relations/SeedingDbContext.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
//using back.DataModels;
|
||||
//using Microsoft.EntityFrameworkCore;
|
||||
|
||||
//namespace back.persistance.data.relations;
|
||||
|
||||
//public class SeedingDbContext : DbContext
|
||||
//{
|
||||
// protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
// {
|
||||
// // 3. CONFIGURAR RELACIONES
|
||||
// modelBuilder.Entity<Role>()
|
||||
// .HasMany(r => r.Permissions)
|
||||
// .WithMany(p => p.Roles)
|
||||
// .UsingEntity<Dictionary<string, object>>(
|
||||
// "RolePermissions",
|
||||
// j => j.HasOne<Permission>().WithMany().HasForeignKey("PermissionsId"),
|
||||
// j => j.HasOne<Role>().WithMany().HasForeignKey("RolesId"),
|
||||
// j => j.HasData(
|
||||
// // Usuario: VIEW_CONTENT y LIKE_CONTENT
|
||||
// new { RolesId = "1", PermissionsId = "1" },
|
||||
// new { RolesId = "1", PermissionsId = "2" },
|
||||
|
||||
// // Content Manager: permisos adicionales
|
||||
// new { RolesId = "2", PermissionsId = "5" },
|
||||
// new { RolesId = "2", PermissionsId = "3" },
|
||||
// new { RolesId = "2", PermissionsId = "4" },
|
||||
// new { RolesId = "2", PermissionsId = "9" },
|
||||
// new { RolesId = "2", PermissionsId = "8" },
|
||||
|
||||
// // Admin: permisos adicionales
|
||||
// new { RolesId = "3", PermissionsId = "6" },
|
||||
// new { RolesId = "3", PermissionsId = "7" },
|
||||
// new { RolesId = "3", PermissionsId = "10" }
|
||||
// )
|
||||
// );
|
||||
|
||||
// // Resto de configuraciones...
|
||||
// base.OnModelCreating(modelBuilder);
|
||||
// }
|
||||
//}
|
15
back/persistance/data/relations/TagRelationEstablisher.cs
Normal file
15
back/persistance/data/relations/TagRelationEstablisher.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class TagRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Tag>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => e.Name, "IX_Tags_Name").IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
32
back/persistance/data/relations/UserRelationEstablisher.cs
Normal file
32
back/persistance/data/relations/UserRelationEstablisher.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.relations;
|
||||
|
||||
public class UserRelationEstablisher : IRelationEstablisher
|
||||
{
|
||||
public void EstablishRelation(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User>(entity =>
|
||||
{
|
||||
entity.HasOne(d => d.IdNavigation).WithOne(p => p.User)
|
||||
.HasForeignKey<User>(d => d.Id)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull);
|
||||
|
||||
entity.HasMany(d => d.Roles).WithMany(p => p.Users)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"UserRole",
|
||||
r => r.HasOne<Role>().WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
l => l.HasOne<User>().WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.ClientSetNull),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("UserId", "RoleId");
|
||||
j.ToTable("UserRoles");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
|
||||
namespace back.persistance.data.repositories.Abstracts;
|
||||
|
||||
public interface IPermissionRepository : IRepository<Permission>, IScoped
|
||||
{
|
||||
Task SeedDefaultPermissions();
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
|
||||
namespace back.persistance.data.repositories.Abstracts;
|
||||
|
||||
public interface IPersonRepository : IRepository<Person>, IScoped
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
|
||||
namespace back.persistance.data.repositories.Abstracts;
|
||||
|
||||
public interface IPhotoRepository : IRepository<Photo>, IScoped
|
||||
{ }
|
@@ -0,0 +1,10 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
|
||||
namespace back.persistance.data.repositories.Abstracts;
|
||||
|
||||
public interface IRoleRepository : IRepository<Role>, IScoped
|
||||
{
|
||||
Task SeedDefaultRoles();
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
|
||||
namespace back.persistance.data.repositories.Abstracts;
|
||||
|
||||
public interface IUserRepository : IRepository<User>, IScoped
|
||||
{
|
||||
Task<User?> GetByEmail(string email);
|
||||
Task<string?> GetUserSaltByEmail(string email);
|
||||
Task<User?> Login(string email, string password);
|
||||
Task<bool> ExistsByEmail(string email);
|
||||
//Task<bool> IsContentManager(string userId);
|
||||
}
|
34
back/persistance/data/repositories/PermissionRepository.cs
Normal file
34
back/persistance/data/repositories/PermissionRepository.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
|
||||
namespace back.persistance.data.repositories;
|
||||
|
||||
public class PermissionRepository(DataContext context) : ReadWriteRepository<Permission>(context), IPermissionRepository
|
||||
{
|
||||
// Implement methods specific to Photo repository if needed
|
||||
public async Task SeedDefaultPermissions()
|
||||
{
|
||||
var defaultPermissions = new List<Permission>
|
||||
{
|
||||
Permission.ViewContentPermission,
|
||||
Permission.LikeContentPermission,
|
||||
Permission.EditContentPermission,
|
||||
Permission.DeleteContentPermission,
|
||||
Permission.CreateContentPermission,
|
||||
Permission.EditUserPermission,
|
||||
Permission.DeleteUserPermission,
|
||||
Permission.DisableUserPermission,
|
||||
Permission.CreateUserPermission,
|
||||
Permission.EditWebConfigPermission
|
||||
};
|
||||
foreach (var permission in defaultPermissions)
|
||||
{
|
||||
if (!Entities.Any(p => p.Id == permission.Id))
|
||||
{
|
||||
Entities.Add(permission);
|
||||
}
|
||||
}
|
||||
await SaveChanges();
|
||||
}
|
||||
}
|
10
back/persistance/data/repositories/PersonRepository.cs
Normal file
10
back/persistance/data/repositories/PersonRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
|
||||
namespace back.persistance.data.repositories;
|
||||
|
||||
public class PersonRepository(DataContext context) : ReadWriteRepository<Person>(context), IPersonRepository
|
||||
{
|
||||
// Implement methods specific to Photo repository if needed
|
||||
}
|
10
back/persistance/data/repositories/PhotoRepository.cs
Normal file
10
back/persistance/data/repositories/PhotoRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
|
||||
namespace back.persistance.data.repositories;
|
||||
|
||||
public class PhotoRepository(DataContext context) : ReadWriteRepository<Photo>(context), IPhotoRepository
|
||||
{
|
||||
// Implement methods specific to Photo repository if needed
|
||||
}
|
27
back/persistance/data/repositories/RoleRepository.cs
Normal file
27
back/persistance/data/repositories/RoleRepository.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
|
||||
namespace back.persistance.data.repositories;
|
||||
|
||||
public class RoleRepository(DataContext context) : ReadWriteRepository<Role>(context), IRoleRepository
|
||||
{
|
||||
// Implement methods specific to Photo repository if needed
|
||||
public async Task SeedDefaultRoles()
|
||||
{
|
||||
var defaultRoles = new List<Role>
|
||||
{
|
||||
Role.AdminRole,
|
||||
Role.UserRole,
|
||||
Role.ContentManagerRole
|
||||
};
|
||||
foreach (var role in defaultRoles)
|
||||
{
|
||||
if (!Entities.Any(p => p.Id == role.Id))
|
||||
{
|
||||
Entities.Add(role);
|
||||
}
|
||||
}
|
||||
await SaveChanges();
|
||||
}
|
||||
}
|
75
back/persistance/data/repositories/UserRepository.cs
Normal file
75
back/persistance/data/repositories/UserRepository.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using MCVIngenieros.Transactional.Implementations.EntityFramework;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.repositories;
|
||||
|
||||
public class UserRepository(
|
||||
DataContext context
|
||||
) : ReadWriteRepository<User>(context), IUserRepository
|
||||
{
|
||||
public async Task<User?> GetByEmail(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(email)) return null;
|
||||
return await Entities.FirstOrDefaultAsync(u => u.Email == email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetUserSaltByEmail(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(email)) return string.Empty;
|
||||
var user = await Entities.FirstOrDefaultAsync(u => u.Email == email);
|
||||
return user?.Salt ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<User?> Login(string email, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
|
||||
try
|
||||
{
|
||||
return await Entities
|
||||
.Include(u => u.Roles)
|
||||
.ThenInclude(r => r.Permissions)
|
||||
.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByEmail(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(email)) return false;
|
||||
return await Entities.AnyAsync(u => u.Email == email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//public async Task<bool> IsContentManager(string userId)
|
||||
//{
|
||||
// var user = await GetById(userId);
|
||||
// if (user == null)
|
||||
// return false;
|
||||
// return user.Roles.Any(role => role.IsContentManager() || role.IsAdmin());
|
||||
//}
|
||||
}
|
8
back/persistance/data/seeders/ISeeder.cs
Normal file
8
back/persistance/data/seeders/ISeeder.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.seeders;
|
||||
|
||||
public interface ISeeder
|
||||
{
|
||||
void Seed(ModelBuilder modelBuilder);
|
||||
}
|
23
back/persistance/data/seeders/PermissionSeeder.cs
Normal file
23
back/persistance/data/seeders/PermissionSeeder.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.seeders;
|
||||
|
||||
public class PermissionSeeder : ISeeder
|
||||
{
|
||||
public void Seed(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Permission>().HasData(
|
||||
Permission.ViewContentPermission,
|
||||
Permission.LikeContentPermission,
|
||||
Permission.EditContentPermission,
|
||||
Permission.DeleteContentPermission,
|
||||
Permission.CreateContentPermission,
|
||||
Permission.EditUserPermission,
|
||||
Permission.DeleteUserPermission,
|
||||
Permission.DisableUserPermission,
|
||||
Permission.CreateUserPermission,
|
||||
Permission.EditWebConfigPermission
|
||||
);
|
||||
}
|
||||
}
|
16
back/persistance/data/seeders/RoleSeeder.cs
Normal file
16
back/persistance/data/seeders/RoleSeeder.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using back.DataModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace back.persistance.data.seeders;
|
||||
|
||||
public class RoleSeeder : ISeeder
|
||||
{
|
||||
public void Seed(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Permission>().HasData(
|
||||
new Role { Id = "1", Name = "User", Description = "Role for regular users", BaseRoleModelId = null },
|
||||
new Role { Id = "2", Name = "Content Manager", Description = "Role for managing content", BaseRoleModelId = "1" },
|
||||
new Role { Id = "3", Name = "Admin", Description = "Administrator role with full permissions", BaseRoleModelId = "2" }
|
||||
);
|
||||
}
|
||||
}
|
14
back/persistance/data/seeders/SystemUserSeeder.cs
Normal file
14
back/persistance/data/seeders/SystemUserSeeder.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
//using back.DataModels;
|
||||
//using Microsoft.EntityFrameworkCore;
|
||||
|
||||
//namespace back.persistance.data.seeders;
|
||||
|
||||
//public class SystemUserSeeder : ISeeder
|
||||
//{
|
||||
// public void Seed(ModelBuilder modelBuilder)
|
||||
// {
|
||||
// modelBuilder.Entity<Permission>().HasData(
|
||||
// User.SystemUser
|
||||
// );
|
||||
// }
|
||||
//}
|
11
back/services/bussines/Errors.cs
Normal file
11
back/services/bussines/Errors.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Net;
|
||||
|
||||
namespace back.services.bussines;
|
||||
|
||||
public static class Errors
|
||||
{
|
||||
public static readonly HttpErrorMap Unauthorized =
|
||||
new(HttpStatusCode.Unauthorized, "Invalid user data. Email or password are wrong.");
|
||||
public static readonly HttpErrorMap BadRequest =
|
||||
new(HttpStatusCode.BadRequest, "Missing user data.");
|
||||
}
|
5
back/services/bussines/HttpErrorMap.cs
Normal file
5
back/services/bussines/HttpErrorMap.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System.Net;
|
||||
|
||||
namespace back.services.bussines;
|
||||
|
||||
public record HttpErrorMap(HttpStatusCode Code, string Description);
|
15
back/services/bussines/PhotoService/IPhotoService.cs
Normal file
15
back/services/bussines/PhotoService/IPhotoService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using back.DataModels;
|
||||
using back.DTO;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.bussines.PhotoService;
|
||||
|
||||
public interface IPhotoService: IScoped
|
||||
{
|
||||
Task<Photo?> Create(PhotoFormModel form);
|
||||
Task Delete(string id, string userId = "00000000-0000-0000-0000-000000000001");
|
||||
Task<Photo?> Get(string id, string userId = "00000000-0000-0000-0000-000000000001");
|
||||
Task<(string? mediaType, byte[]? fileBytes)> GetBytes(string id, string res = "");
|
||||
Task<(int totalItems, IEnumerable<Photo>? pageData)> GetPage(int page, int pageSize);
|
||||
Task<Photo?> Update(Photo photo, string userId = "00000000-0000-0000-0000-000000000001");
|
||||
}
|
82
back/services/bussines/PhotoService/PhotoService.cs
Normal file
82
back/services/bussines/PhotoService/PhotoService.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using back.DataModels;
|
||||
using back.DTO;
|
||||
using back.persistance.blob;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
|
||||
namespace back.services.bussines.PhotoService;
|
||||
|
||||
public class PhotoService(
|
||||
IPhotoRepository photoRepository,
|
||||
IUserRepository userRepository,
|
||||
IBlobStorageService blobStorageService
|
||||
) : IPhotoService
|
||||
{
|
||||
public async Task<Photo?> Create(PhotoFormModel form)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(form);
|
||||
if (form.Image == null || form.Image.Length == 0)
|
||||
throw new ArgumentException("No image uploaded.", nameof(form));
|
||||
//if (string.IsNullOrEmpty(form.UserId) || await userRepository.IsContentManager(form.UserId))
|
||||
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(form.UserId));
|
||||
|
||||
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task Delete(string id, string userId = User.SystemUserId)
|
||||
{
|
||||
//if (string.IsNullOrEmpty(userId) || await userRepository.IsContentManager(userId))
|
||||
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(userId));
|
||||
photoRepository.Delete(id);
|
||||
}
|
||||
|
||||
public async Task<Photo?> Get(string id, string userId = User.SystemUserId)
|
||||
{
|
||||
Photo? photo = await photoRepository.GetById(id);
|
||||
return photo;
|
||||
//return photo?.CanBeSeenBy(userId) ?? false
|
||||
// ? photo
|
||||
// : null;
|
||||
}
|
||||
|
||||
public async Task<(string? mediaType, byte[]? fileBytes)> GetBytes(string id, string res = "")
|
||||
{
|
||||
var photo = await photoRepository.GetById(id);
|
||||
if (photo == null)
|
||||
return (null, null);
|
||||
|
||||
string filePath = res.ToLower() switch
|
||||
{
|
||||
"high" => photo.HighResUrl,
|
||||
"mid" => photo.MidResUrl,
|
||||
"low" or _ => photo.LowResUrl
|
||||
};
|
||||
|
||||
string? mediaType = res.ToLower() switch
|
||||
{
|
||||
"high" => $"image/{photo.Extension}",
|
||||
"mid" or "low" or _ => "image/webp",
|
||||
};
|
||||
|
||||
return (
|
||||
mediaType,
|
||||
await blobStorageService.GetBytes(filePath) ?? throw new FileNotFoundException("File not found.", filePath)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<(int totalItems, IEnumerable<Photo>? pageData)> GetPage(int page, int pageSize)
|
||||
{
|
||||
return (
|
||||
totalItems: await photoRepository.GetTotalItems(),
|
||||
pageData: photoRepository.GetPage(page, pageSize)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Photo?> Update(Photo photo, string userId = "00000000-0000-0000-0000-000000000001")
|
||||
{
|
||||
//if (string.IsNullOrEmpty(userId) || await userRepository.IsContentManager(userId))
|
||||
// throw new ArgumentException("Invalid user ID or user is not a content manager.", nameof(userId));
|
||||
return await photoRepository.Update(photo);
|
||||
}
|
||||
}
|
13
back/services/bussines/UserService/IUserService.cs
Normal file
13
back/services/bussines/UserService/IUserService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using back.DataModels;
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.bussines.UserService;
|
||||
|
||||
public interface IUserService: IScoped
|
||||
{
|
||||
Task<User?> Create(string clientId, User user);
|
||||
Task<User?> Login(string email, string password, string clientId);
|
||||
Task SendResetPassword(string email);
|
||||
Task<User?> Update(User user);
|
||||
Task<User?> ValidateSystemUser(string email, string password, string systemKey, string clientId);
|
||||
}
|
142
back/services/bussines/UserService/UserService.cs
Normal file
142
back/services/bussines/UserService/UserService.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.blob;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using back.services.engine.Crypto;
|
||||
using back.services.engine.mailing;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace back.services.bussines.UserService;
|
||||
|
||||
public class UserService(
|
||||
IUserRepository userRepository, ICryptoService cryptoService,
|
||||
IEmailService emailService,
|
||||
IBlobStorageService blobStorageService,
|
||||
JsonSerializerOptions jsonSerializerOptions
|
||||
) : IUserService
|
||||
{
|
||||
private readonly IUserRepository _repository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
|
||||
private readonly ICryptoService _cryptoService = cryptoService;
|
||||
private readonly IEmailService _emailService = emailService;
|
||||
private readonly IBlobStorageService _blobStorageService = blobStorageService;
|
||||
|
||||
public async Task<User?> Create(string clientId, User user)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
if (user.Id != null && await _repository.Exists(user.Id))
|
||||
{
|
||||
return await _repository.GetById(user.Id);
|
||||
}
|
||||
if (string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (await _repository.Exists(user.Email))
|
||||
{
|
||||
return await _repository.GetByEmail(user.Email);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(user.Salt))
|
||||
{
|
||||
user.Salt = _cryptoService.Salt();
|
||||
}
|
||||
user.Password = _cryptoService.Decrypt(clientId, user.Password) ?? string.Empty;
|
||||
user.Password = _cryptoService.HashPassword(user.Password, user.Salt) ?? string.Empty;
|
||||
|
||||
user.CreatedAt = DateTimeOffset.UtcNow.ToString("dd-MM-yyyy HH:mm:ss zz");
|
||||
|
||||
//user.Roles.Add(Role.UserRole);
|
||||
|
||||
await _repository.Insert(user);
|
||||
await _repository.SaveChanges();
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User?> Update(User user)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
if (user.Id == null || !await _repository.Exists(user.Id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var existingUser = await _repository.GetById(user.Id);
|
||||
if (existingUser == null) return null;
|
||||
existingUser.Email = user.Email;
|
||||
await _repository.Update(existingUser);
|
||||
await _repository.SaveChanges();
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
public async Task<User?> Login(string email, string decryptedPass)
|
||||
{
|
||||
var salt = await _repository.GetUserSaltByEmail(email);
|
||||
var hashedPassword = _cryptoService.HashPassword(decryptedPass, salt);
|
||||
var user = await _repository.Login(email, hashedPassword ?? string.Empty);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User?> Login(string email, string password, string clientId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var decryptedPass = _cryptoService.Decrypt(clientId, password);
|
||||
var user = await Login(email, decryptedPass ?? string.Empty);
|
||||
return user;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendResetPassword(string email)
|
||||
{
|
||||
var exists = await _repository.ExistsByEmail(email);
|
||||
if (!exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await _emailService.SendEmailAsync(
|
||||
tos: email,
|
||||
from: "admin@mmorales.photo",
|
||||
subject: "Reset Password",
|
||||
body: "If you received this email, it means that you have requested a password reset. Please follow the instructions in the email to reset your password."
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<User?> ValidateSystemUser(string email, string password, string systemKey, string clientId)
|
||||
{
|
||||
var decryptedPassword = _cryptoService.Decrypt(clientId, password) ?? string.Empty;
|
||||
var decryptedsystemKey = _cryptoService.Decrypt(clientId, systemKey) ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(decryptedPassword) || string.IsNullOrEmpty(decryptedsystemKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!email.Equals(User.SystemUser.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var systemKeyBytes = await _blobStorageService.GetBytes("systemkey.lock");
|
||||
var systemKeyString = Encoding.UTF8.GetString(systemKeyBytes ?? []);
|
||||
var systemKeyObject = JsonSerializer.Deserialize<SystemKey>(systemKeyString, jsonSerializerOptions);
|
||||
if (systemKeyObject == null || !systemKeyObject.IsValid(email, decryptedPassword, decryptedsystemKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!await _repository.ExistsByEmail(email))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var user = await _repository.GetByEmail(email);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var loggedUser = await Login(user.Email!, decryptedPassword);
|
||||
return loggedUser;
|
||||
}
|
||||
}
|
175
back/services/engine/Crypto/CryptoService.cs
Normal file
175
back/services/engine/Crypto/CryptoService.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace back.services.engine.Crypto;
|
||||
|
||||
public class CryptoService(IMemoryCache cache) : ICryptoService
|
||||
{
|
||||
private readonly IMemoryCache _cache = cache;
|
||||
private readonly MemoryCacheEntryOptions _CacheOptions = new()
|
||||
{
|
||||
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1),
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
|
||||
SlidingExpiration = TimeSpan.FromMinutes(30),
|
||||
Priority = CacheItemPriority.High,
|
||||
PostEvictionCallbacks =
|
||||
{
|
||||
new PostEvictionCallbackRegistration
|
||||
{
|
||||
EvictionCallback = (key, value, reason, state) =>
|
||||
{
|
||||
var clientId = key.ToString()?.Replace("_public","").Replace("_private","");
|
||||
if(string.IsNullOrEmpty(clientId)) { return; }
|
||||
// Handle the eviction of the certificate - removing public/private keys from the cache
|
||||
try{ cache.Remove($"{clientId}_public"); } catch{ }
|
||||
try{ cache.Remove($"{clientId}_private"); } catch{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public string? Encrypt(string clientId,string plainText)
|
||||
{
|
||||
// get keys from cache
|
||||
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
throw new InvalidOperationException("Public certificate not found for the client.");
|
||||
}
|
||||
// import rsa keys and configure RSA for encryption
|
||||
using var rsa = RSA.Create(2048);
|
||||
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
|
||||
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
|
||||
// Encrypt the plain text using RSA
|
||||
string? encryptedText = null;
|
||||
try
|
||||
{
|
||||
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
var encryptedBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
encryptedText = Convert.ToBase64String(encryptedBytes);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
// Handle encryption errors
|
||||
throw new InvalidOperationException("Encryption failed.", ex);
|
||||
}
|
||||
return encryptedText;
|
||||
}
|
||||
|
||||
public string? Decrypt(string clientId, string encryptedText)
|
||||
{
|
||||
// get keys from cache
|
||||
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
throw new InvalidOperationException("Private certificate not found for the client.");
|
||||
}
|
||||
// import rsa keys and configure RSA for decryption
|
||||
using var rsa = RSA.Create(2048);
|
||||
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
|
||||
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
|
||||
// Decrypt the encrypted text using RSA
|
||||
string? plainText = null;
|
||||
try
|
||||
{
|
||||
var encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||
var decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
|
||||
plainText = System.Text.Encoding.UTF8.GetString(decryptedBytes);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
// Handle decryption errors
|
||||
throw new InvalidOperationException("Decryption failed.", ex);
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
|
||||
public string GetPublicCertificate(string clientId)
|
||||
{
|
||||
if (_cache.TryGetValue($"{clientId}_public", out string? publicCert) && !string.IsNullOrEmpty(publicCert))
|
||||
{
|
||||
return publicCert;
|
||||
}
|
||||
(publicCert, string privateCert) = GenerateCertificate();
|
||||
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
|
||||
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
|
||||
return publicCert;
|
||||
}
|
||||
|
||||
public string GetPrivateCertificate(string clientId)
|
||||
{
|
||||
if (_cache.TryGetValue($"{clientId}_private", out string? privateCert) && !string.IsNullOrEmpty(privateCert))
|
||||
{
|
||||
return privateCert;
|
||||
}
|
||||
(string publicCert, privateCert) = GenerateCertificate();
|
||||
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
|
||||
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
|
||||
return privateCert;
|
||||
}
|
||||
|
||||
private static (string publicCert, string privateCert) GenerateCertificate()
|
||||
{
|
||||
// Generate a new RSA key pair for the client
|
||||
using var rsa = RSA.Create(2048);
|
||||
var publicKey = rsa.ExportSubjectPublicKeyInfo();
|
||||
var privateKey = rsa.ExportRSAPrivateKey();
|
||||
// Convert to Base64 strings for storage
|
||||
var publicCert = Convert.ToBase64String(publicKey);
|
||||
var privateCert = Convert.ToBase64String(privateKey);
|
||||
return (publicCert, privateCert);
|
||||
}
|
||||
|
||||
public string? Hash(string plainText)
|
||||
{
|
||||
string? hash = null;
|
||||
if (string.IsNullOrEmpty(plainText))
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
var hashBytes = SHA256.HashData(plainBytes);
|
||||
hash = Convert.ToBase64String(hashBytes);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public bool VerifyHash(string plainText, string hash)
|
||||
{
|
||||
var plainTextHash = Hash(plainText);
|
||||
if (string.IsNullOrEmpty(plainTextHash) || string.IsNullOrEmpty(hash))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return plainTextHash.Equals(hash, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public string Pepper()
|
||||
{
|
||||
// get pepper from environtment variable
|
||||
var pepper = Environment.GetEnvironmentVariable("PEPPER");
|
||||
if (string.IsNullOrEmpty(pepper))
|
||||
{
|
||||
return "BactilForteFlash20mg";
|
||||
}
|
||||
return pepper;
|
||||
}
|
||||
|
||||
public string Salt()
|
||||
{
|
||||
var saltBytes = new byte[32]; // 256 bits
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(saltBytes);
|
||||
return Convert.ToBase64String(saltBytes);
|
||||
}
|
||||
|
||||
public string? HashPassword(string plainPassword, string plainSalt)
|
||||
{
|
||||
return Hash($"{plainPassword}{plainSalt}{Pepper()}");
|
||||
}
|
||||
}
|
16
back/services/engine/Crypto/ICryptoService.cs
Normal file
16
back/services/engine/Crypto/ICryptoService.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.engine.Crypto;
|
||||
|
||||
public interface ICryptoService : ISingleton
|
||||
{
|
||||
string? Encrypt(string clientId, string plainText);
|
||||
string? Decrypt(string clientId, string encryptedText);
|
||||
string? Hash(string plainText);
|
||||
string? HashPassword(string? plainPassword, string? plainSalt);
|
||||
bool VerifyHash(string plainText, string hash);
|
||||
string Salt();
|
||||
string Pepper();
|
||||
string GetPublicCertificate(string clientId);
|
||||
string GetPrivateCertificate(string clientId);
|
||||
}
|
8
back/services/engine/ImageResizer/IImageResizer.cs
Normal file
8
back/services/engine/ImageResizer/IImageResizer.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.engine.ImageResizer;
|
||||
|
||||
public interface IImageResizer : ISingleton
|
||||
{
|
||||
Task<Stream> ResizeImage(IFormFile image, int v);
|
||||
}
|
23
back/services/engine/ImageResizer/ImageResizer.cs
Normal file
23
back/services/engine/ImageResizer/ImageResizer.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace back.services.engine.ImageResizer;
|
||||
|
||||
public sealed class ImageResizer : IImageResizer
|
||||
{
|
||||
public async Task<Stream> ResizeImage(IFormFile image, int maxRes)
|
||||
{
|
||||
if (image == null || image.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid image file.");
|
||||
}
|
||||
using var inputStream = image.OpenReadStream();
|
||||
using var outputStream = new MemoryStream();
|
||||
using var img = Image.Load(inputStream);
|
||||
|
||||
img.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(maxRes, 0), Mode = ResizeMode.Max }));
|
||||
await img.SaveAsWebpAsync(outputStream);
|
||||
outputStream.Position = 0;
|
||||
return outputStream;
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.engine.PasswordGenerator;
|
||||
|
||||
public interface IPasswordGenerator : ISingleton
|
||||
{
|
||||
string Generate(int length, bool includeNumbers = true, bool includeMayus = true, bool includeMinus = true, bool includeSpecials = true);
|
||||
}
|
40
back/services/engine/PasswordGenerator/PasswordGenerator.cs
Normal file
40
back/services/engine/PasswordGenerator/PasswordGenerator.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace back.services.engine.PasswordGenerator;
|
||||
|
||||
public class PasswordGenerator : IPasswordGenerator
|
||||
{
|
||||
public string Generate(int length, bool includeNumbers = true, bool includeMayus = true, bool includeMinus = true, bool includeSpecials = true)
|
||||
{
|
||||
const string numbers = "0123456789";
|
||||
const string mayus = "ABCÇDEFGHIJKLMNÑOPQRSTUVWXYZ";
|
||||
const string minus = "abcçdefghijklmnñopqrstuvwxyz";
|
||||
const string specials = "!@#$%^&*()_+[]{}|;:,.<>?";
|
||||
var characters = minus;
|
||||
if (includeNumbers) characters += numbers;
|
||||
if (includeMayus) characters += mayus;
|
||||
if (includeSpecials) characters += specials;
|
||||
var random = new Random((int)DateTimeOffset.UtcNow.Ticks);
|
||||
var password = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
password[i] = characters[random.Next(characters.Length)];
|
||||
}
|
||||
|
||||
var positionPool = new List<int>();
|
||||
for (int i = 0; i < length; i++) positionPool.Add(i);
|
||||
var forcedRandomNumber = random.Next(0, positionPool.Count);
|
||||
positionPool.RemoveAt(forcedRandomNumber);
|
||||
var forcedRandomMayus = random.Next(0, positionPool.Count);
|
||||
positionPool.RemoveAt(forcedRandomMayus);
|
||||
var forcedRandomMinus = random.Next(0, positionPool.Count);
|
||||
positionPool.RemoveAt(forcedRandomMinus);
|
||||
var forcedRandomSpecial = random.Next(0, positionPool.Count);
|
||||
positionPool.RemoveAt(forcedRandomSpecial);
|
||||
|
||||
password[forcedRandomNumber] = numbers[random.Next(numbers.Length)];
|
||||
password[forcedRandomMayus] = mayus[random.Next(mayus.Length)];
|
||||
password[forcedRandomMinus] = minus[random.Next(minus.Length)];
|
||||
password[forcedRandomSpecial] = specials[random.Next(specials.Length)];
|
||||
return new string(password);
|
||||
}
|
||||
}
|
8
back/services/engine/SystemUser/ISystemUserGenerator.cs
Normal file
8
back/services/engine/SystemUser/ISystemUserGenerator.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.engine.SystemUser;
|
||||
|
||||
public interface ISystemUserGenerator: IScoped
|
||||
{
|
||||
Task GenerateAsync();
|
||||
}
|
60
back/services/engine/SystemUser/SystemUserGenerator.cs
Normal file
60
back/services/engine/SystemUser/SystemUserGenerator.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using back.DataModels;
|
||||
using back.persistance.blob;
|
||||
using back.persistance.data;
|
||||
using back.persistance.data.repositories.Abstracts;
|
||||
using back.services.engine.Crypto;
|
||||
using back.services.engine.PasswordGenerator;
|
||||
using MCVIngenieros.Transactional.Abstractions.Interfaces;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace back.services.engine.SystemUser;
|
||||
|
||||
public class SystemUserGenerator(
|
||||
ITransactionalService<DataContext> transactional,
|
||||
JsonSerializerOptions jsonSerializerOptions,
|
||||
IUserRepository userRepository,
|
||||
IPersonRepository personRepository,
|
||||
IRoleRepository roleRepository,
|
||||
IPermissionRepository permissionRepository,
|
||||
ICryptoService cryptoService,
|
||||
IBlobStorageService blobStorageService,
|
||||
IPasswordGenerator passwordGenerator) : ISystemUserGenerator
|
||||
{
|
||||
public async Task GenerateAsync()
|
||||
{
|
||||
var systemKey = new SystemKey()
|
||||
{
|
||||
Password = passwordGenerator.Generate(16),
|
||||
};
|
||||
var systemKeyJson = JsonSerializer.Serialize(systemKey, options: jsonSerializerOptions);
|
||||
|
||||
using Stream stream = new MemoryStream(new System.Text.UTF8Encoding(true).GetBytes(systemKeyJson));
|
||||
|
||||
await blobStorageService.Delete("systemkey.lock");
|
||||
|
||||
await blobStorageService.Save(
|
||||
stream,
|
||||
"systemkey.lock"
|
||||
);
|
||||
|
||||
User.SystemUser.Password = systemKey.Password;
|
||||
User.SystemUser.Salt = cryptoService.Salt();
|
||||
User.SystemUser.Password = cryptoService.HashPassword(User.SystemUser.Password, User.SystemUser.Salt) ?? string.Empty;
|
||||
|
||||
if (!await userRepository.Exists(User.SystemUser.Id!))
|
||||
{
|
||||
await transactional.DoTransaction(async () =>
|
||||
{
|
||||
await permissionRepository.SeedDefaultPermissions();
|
||||
await roleRepository.SeedDefaultRoles();
|
||||
await personRepository.Insert(Person.SystemPerson);
|
||||
await userRepository.Insert(User.SystemUser);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await userRepository.Update(User.SystemUser);
|
||||
await userRepository.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
66
back/services/engine/mailing/EmailService.cs
Normal file
66
back/services/engine/mailing/EmailService.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using back.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace back.services.engine.mailing;
|
||||
|
||||
public class EmailService(IOptions<MailServerOptions> options) : IEmailService
|
||||
{
|
||||
public async Task SendEmailAsync(List<string> tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Parallel.ForEachAsync(tos, async (to, cancellationToken) => {
|
||||
await SendEmailAsync(to, from, subject, body, attachments, cancellationToken);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception or handle it as needed
|
||||
Console.WriteLine($"Error sending email to multiple recipients: {ex.Message}");
|
||||
}
|
||||
}
|
||||
public async Task SendEmailAsync(string to, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var message = new MailMessage();
|
||||
message.From = new MailAddress(from);
|
||||
message.To.Add(to);
|
||||
message.Subject = subject;
|
||||
message.Body = body;
|
||||
message.IsBodyHtml = true;
|
||||
message.Priority = MailPriority.Normal;
|
||||
message.DeliveryNotificationOptions = DeliveryNotificationOptions.Never;
|
||||
|
||||
if (attachments != null)
|
||||
{
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
if (attachment.Value is FileStream fileStream)
|
||||
{
|
||||
message.Attachments.Add(new Attachment(fileStream, attachment.Key));
|
||||
}
|
||||
if (attachment.Value is string filePath && File.Exists(filePath))
|
||||
{
|
||||
message.Attachments.Add(new Attachment(filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var cliente = new SmtpClient(options.Value.SmtpServer, options.Value.Puerto);
|
||||
cliente.UseDefaultCredentials = false;
|
||||
cliente.Credentials = new NetworkCredential(options.Value.Usuario, options.Value.Password);
|
||||
cliente.EnableSsl = options.Value.EnableSsl;
|
||||
cliente.DeliveryMethod = SmtpDeliveryMethod.Network;
|
||||
|
||||
await cliente.SendMailAsync(message, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception or handle it as needed
|
||||
Console.WriteLine($"Error sending email: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
9
back/services/engine/mailing/IEmailService.cs
Normal file
9
back/services/engine/mailing/IEmailService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using DependencyInjector.Abstractions.Lifetimes;
|
||||
|
||||
namespace back.services.engine.mailing;
|
||||
|
||||
public interface IEmailService : IScoped
|
||||
{
|
||||
Task SendEmailAsync(List<string> tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default);
|
||||
Task SendEmailAsync(string tos, string from, string subject, string body, Dictionary<string, object>? attachments = null, CancellationToken cancellationToken = default);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user