Switches the database from SQL Server to MariaDB

The target Pi only has MariaDB, and SQL Server has no ARM64 build at
all - not a config problem, a real gap discovered during deployment
setup. Swapped the EF Core provider, regenerated every migration,
updated connection strings and the backup script everywhere they
appear.

Took two tries to land on a provider that actually works: Pomelo
builds fine against this project's EF Core 10 packages but fails at
runtime (it's compiled against 9's internal API surface, which moved
in 10 wherever Identity/DataProtection force the newer packages).
Oracle's official provider builds and migrates fine but has a real
MariaDB bug in its own migration-lock code, reproduced against a live
database. Kept Oracle's provider and worked around just that one
broken method - everything else it does is correct - rather than
give up more of the stack to chase a workaround.

Verified against a real local MariaDB end to end: all three
migrations applied, both hosts start clean, full suite still green.
This commit is contained in:
2026-07-29 11:59:09 +02:00
parent 579e0ceaac
commit 33d18ebbf8
33 changed files with 575 additions and 1367 deletions
+9 -4
View File
@@ -13,13 +13,18 @@ Een modulaire monolith CMS gebouwd met .NET 10.
### Vereisten ### Vereisten
- .NET 10 SDK - .NET 10 SDK
- Podman of Docker (voor SQL Server) - Podman of Docker (voor MariaDB)
### 1. Database opstarten ### 1. Database opstarten
Start een SQL Server container met de volgende opdracht: Start een MariaDB-container met de volgende opdracht:
```powershell ```powershell
podman run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<your_password>" -p 1433:1433 --name sql-server -d mcr.microsoft.com/mssql/server:2022-latest podman run -e "MARIADB_ROOT_PASSWORD=<your_password>" -p 3306:3306 --name mariadb -d docker.io/library/mariadb:latest
``` ```
> **Migratie-notitie**: dit project draaide tot 2026-07-28 op Microsoft SQL Server. Overgestapt naar
> MariaDB omdat de productie-Pi geen SQL Server kan draaien (geen ARM64-build bestaat) en al MariaDB
> heeft geïnstalleerd. Zie `aidlc-docs/features/gitea-deployment-workflow/` voor de volledige
> rationale. Bestaande lokale SQL Server-databases/migraties zijn niet compatibel — begin met een
> verse MariaDB-database.
### 2. Configuratie ### 2. Configuratie
De applicatie maakt gebruik van een drie-bestanden patroon voor configuratie: De applicatie maakt gebruik van een drie-bestanden patroon voor configuratie:
@@ -31,7 +36,7 @@ Zorg dat er een `src/SlpModularCms.Api/appsettings.local.json` aanwezig is met d
```json ```json
{ {
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1,1433;User ID=sa;Password=<your_password>;Database=SlpModularCms;TrustServerCertificate=True;MultipleActiveResultSets=true" "DefaultConnection": "Server=127.0.0.1;Port=3306;Database=SlpModularCms;Uid=root;Pwd=<your_password>"
} }
} }
``` ```
@@ -57,6 +57,72 @@
- **Artifacts**: `operations/deployment/deployment-plan.md`, `deployment-instructions.md`, - **Artifacts**: `operations/deployment/deployment-plan.md`, `deployment-instructions.md`,
`rollback-plan.md` `rollback-plan.md`
### Database Provider Migration — SQL Server → MariaDB (2026-07-29)
Discovered while finalizing Deployment Setup: the target Pi only runs MariaDB, and Microsoft SQL
Server has **no ARM64 build at all** (the `mcr.microsoft.com/mssql/server` image is `linux/amd64`
only; Azure SQL Edge, the former ARM answer, is retired). This invalidates ASM-04. Confirmed no
other machine is available, and the eventual production host (`mijnhostingpartner.nl`) will also
run MariaDB, plus the workload is small enough that MariaDB's performance is not a concern — user
decided: switch the database provider, not the deployment target.
**What changed** (application code, not just Operations docs):
- `SlpModularCms.Core.csproj`: `Microsoft.EntityFrameworkCore.SqlServer``MySql.EntityFrameworkCore`
10.0.7 (Oracle's official provider)
- `UseSqlServer(...)``UseMySQL(...)` in `ServiceCollectionExtensions.cs`, `AvailabilityModule.cs`,
`MasterModule.cs`
- `DatabaseMigrationExtensions.cs` + its test: `Microsoft.Data.SqlClient.SqlException`
`MySql.Data.MySqlClient.MySqlException` for the transient-failure classifier
- All three DbContexts' migrations deleted and regenerated (`ApplicationDbContext`,
`AvailabilityDbContext`, `MasterDbContext`) — no MySQL/MariaDB model-compatibility issues surfaced
(no index-length problems, no raw SQL anywhere in the codebase to translate)
- Connection strings updated across `appsettings.json` (Api + Api.Slave, all three tiers) from
SQL Server format to MySQL format
- `README.md` dev setup: MariaDB container command replacing the SQL Server one, with a migration
note for anyone returning to old instructions
- `deployment-instructions.md` / `rollback-plan.md`: connection string format, backup script
rewritten around `mariadb-dump` (was `sqlcmd`/`BACKUP DATABASE`), restore procedure rewritten
**Provider selection — two failed attempts before the working one, both reproduced against a real
MariaDB instance, not just reasoned about:**
1. **Pomelo.EntityFrameworkCore.MySql** (the usual first choice, explicit first-class MariaDB
support) — caps at EF Core 9.x, no EF Core 10 release exists. A Pomelo maintainer states
([PR #2017](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/2017))
that Pomelo 9 / EF Core 9 packages work fine on a net10.0 TFM — confirmed true only when nothing
else forces EF Core 10 packages. This project's `Microsoft.AspNetCore.Identity.EntityFrameworkCore`
and `Microsoft.AspNetCore.DataProtection.EntityFrameworkCore` are versioned in lockstep with the
.NET 10 runtime and hard-require EF Core >= 10.0.9, so the resolved `Microsoft.EntityFrameworkCore.Abstractions`
ends up at 10.0.9 regardless. Restore only warns (NU1608), and it builds — but fails at runtime
with `MissingMethodException: AbstractionsStrings.ArgumentIsEmpty` the moment EF tooling touches
a DbContext: Pomelo's compiled assembly calls an internal EF Core 9 helper that no longer exists
in the 10.0.9 assembly actually loaded.
2. **MySql.EntityFrameworkCore 10.0.7** (Oracle's official provider) — its net10.0 dependency group
targets EF Core 10.0.7, compatible with 10.0.9. Builds and migrates cleanly, but
`dotnet ef database update` against the real MariaDB throws
`InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.Int64'` in
`MySQLHistoryRepository.AcquireDatabaseLock()` — a confirmed MariaDB-incompatibility bug (MariaDB's
`GET_LOCK()` apparently returns `NULL` in a case Oracle's code doesn't handle, and Oracle's
provider is tested against real MySQL Server, not MariaDB). This isn't limited to CLI tooling —
the same code path runs on every application startup via `MigrateCoreDatabase()`.
**Working solution**: kept Oracle's `MySql.EntityFrameworkCore` 10.0.7 (otherwise fully compatible)
and added `NonLockingMySQLHistoryRepository` (`SlpModularCms.Core/Hosting/`), wired in via
`options.ReplaceService<IHistoryRepository, NonLockingMySQLHistoryRepository>()` on all three
`AddDbContext` registrations. Oracle's internal `MySQLHistoryRepository` class can't be subclassed
(it's `internal`, only its constructor is public), so the workaround constructs a real instance of
it via reflection and forwards every `IHistoryRepository` member to it **except**
`AcquireDatabaseLock`/`AcquireDatabaseLockAsync`, which return a no-op lock instead of ever reaching
the broken `GET_LOCK` call. Accepted as safe because this deployment model never runs migrations
from more than one place at a time (`MigrateCoreDatabase()` at startup, one deploy at a time via the
atomic-release sequence) — a genuinely concurrent multi-instance migration race is not a scenario
this architecture produces.
**Verified end to end**: all three migrations applied successfully to the user's real local
MariaDB (`dotnet ef database update`, all expected tables present including `DataProtectionKeys`
and the renamed Identity tables); both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` start
cleanly against it (`/health` → 200, `MigrateCoreDatabase()` logs "already up to date" on the
second run); full backend suite re-confirmed at 372/372 passed, 0 build errors.
## Scope Decisions (from feature-selection.md) ## Scope Decisions (from feature-selection.md)
- **Public website**: documentation/instructions only — where the website build lands in `wwwroot/`, how it coexists with `wwwroot/admin/`, and what a per-website workspace must deliver. The website's own build/deploy workflow stays out of scope (Q4 = A). - **Public website**: documentation/instructions only — where the website build lands in `wwwroot/`, how it coexists with `wwwroot/admin/`, and what a per-website workspace must deliver. The website's own build/deploy workflow stays out of scope (Q4 = A).
- **Environments**: local, test, production only. - **Environments**: local, test, production only.
@@ -1345,3 +1345,14 @@ by Infrastructure Design. U5 carries REF-U3-01's Umami origin gate plus FR-21 an
**Context**: OPERATIONS — Deployment Setup, configuration guidance clarified before final approval **Context**: OPERATIONS — Deployment Setup, configuration guidance clarified before final approval
--- ---
## Database Provider Migration — SQL Server to MariaDB
**Timestamp**: 2026-07-29T00:00:00Z
**User Input**: "Op de Pi draait op dit moment alleen MariaDB en geen mssql. Maakt dat uit?" ... "Nee, geen andere machine, maar kan het misschien met een podman container geregeld worden? Die draait wel op die machine" ... "De beoogde productieomgeving zal ook MariaDB hebben (mijnhostingpartner.nl) en de websites zullen niet mega groot worden dus ik denk dat voor performance MAriaDB ook prima is. Laten we optie 1 doen met MariaDB. Ik heb de lokale connection string aangepast. Die zou moeten werken. Dus als het lukt mag je meteen de migraties mogen runnen als dat nodig is" ... "In deze pull request: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/2017 [...] Als je dat kan controleren dan zouden we door kunnen" ... "Ja, probeer die override"
**AI Response**: "Identified that SQL Server has no ARM64 build and Azure SQL Edge (the former ARM path) is retired, explained the tradeoffs (remote SQL Server, QEMU emulation, or switching to MariaDB), user chose MariaDB. Swapped Microsoft.EntityFrameworkCore.SqlServer for MySql.EntityFrameworkCore across Core.csproj and the 3 UseSqlServer call sites, regenerated all migrations, updated connection strings and README/deployment docs. Tried Pomelo per the user's PR link — confirmed it restores/builds against EF Core 10.0.9 (matching the maintainer's claim) but fails at runtime with a MissingMethodException, since this project's Identity/DataProtection EF Core integration packages force EF Core 10 packages regardless of Pomelo's own 9.x dependency range. Reverted to Oracle's provider, which then surfaced a second, independent bug: AcquireDatabaseLock() throws InvalidCastException against real MariaDB (GET_LOCK() returns NULL where Oracle's code expects an integer). Implemented NonLockingMySQLHistoryRepository as a targeted workaround (constructs Oracle's internal history repository via reflection, forwards everything except the two lock methods). Verified end to end: all 3 migrations applied to the user's real local MariaDB, both hosts (Api and Api.Slave) start cleanly against it, full 372-test backend suite still green. Corrected ASM-04 in requirements.md and recorded the full saga in aidlc-state.md."
**Context**: OPERATIONS — Deployment Setup revealed a genuine Construction-phase technology gap; resolved with application code changes (not just documentation), verified against a real database
---
@@ -331,7 +331,7 @@ DEV-01…04 are **pre-existing or cost-driven** and none is introduced by this f
| ASM-01 | With the atomic release switch (FR-06), `wwwroot/web/` must live **outside** the swapped release directory and be linked into it (e.g. a symlink to a persistent path on the host). | Otherwise switching releases silently discards the customer's website — precisely the failure mode D-06 was chosen to prevent. This follows necessarily from combining Q4 = C with CQ3 = C, so it is stated rather than asked. | Raise it and the deploy design changes materially; flag before Construction if this is not acceptable. | | ASM-01 | With the atomic release switch (FR-06), `wwwroot/web/` must live **outside** the swapped release directory and be linked into it (e.g. a symlink to a persistent path on the host). | Otherwise switching releases silently discards the customer's website — precisely the failure mode D-06 was chosen to prevent. This follows necessarily from combining Q4 = C with CQ3 = C, so it is stated rather than asked. | Raise it and the deploy design changes materially; flag before Construction if this is not acceptable. |
| ASM-02 | **No `wwwroot` folder is needed for the API.** The API is not static content — its assemblies live in the application root and it serves `/api/v1` through routing. The option is kept open but nothing is built for it. | Avoids building an unused folder. | If something static under an API path is intended, say so and FR-07 gains a third mount. | | ASM-02 | **No `wwwroot` folder is needed for the API.** The API is not static content — its assemblies live in the application root and it serves `/api/v1` through routing. The option is kept open but nothing is built for it. | Avoids building an unused folder. | If something static under an API path is intended, say so and FR-07 gains a third mount. |
| ASM-03 | The Pi already runs, or can run, a .NET 10 runtime, and the app is managed by a process manager (systemd) that the deploy can restart over SSH. | The atomic switch requires restarting the process. | Restart mechanism changes; deploy step is rewritten. | | ASM-03 | The Pi already runs, or can run, a .NET 10 runtime, and the app is managed by a process manager (systemd) that the deploy can restart over SSH. | The atomic switch requires restarting the process. | Restart mechanism changes; deploy step is rewritten. |
| ASM-04 | The Pi's SQL Server database is reachable from the application, and a backup can be taken before a production deploy. | FR-20 depends on it. | FR-20 becomes a documented manual precondition only. | | ASM-04 | ~~The Pi's SQL Server database is reachable from the application~~, and a backup can be taken before a production deploy. | FR-20 depends on it. | **Wrong — corrected at Deployment Setup, 2026-07-29**: SQL Server has no ARM64 build at all, and the Pi only runs MariaDB. The EF Core provider was migrated from `Microsoft.EntityFrameworkCore.SqlServer` to `MySql.EntityFrameworkCore` (Oracle's official MySQL/MariaDB provider), all migrations regenerated, connection strings and the backup script (`mariadb-dump`, not `sqlcmd`) updated accordingly. See `aidlc-state.md` § Operations for the full record, including a confirmed MariaDB-compatibility bug in Oracle's provider (`AcquireDatabaseLock`) and its workaround (`NonLockingMySQLHistoryRepository`). |
| ASM-05 | The existing Umami instance at `analytics.slpsoftware.nl` remains available and its script origin can be added to the CSP. | FR-16, FR-18. | Umami setup gains host work, as in the reference project. | | ASM-05 | The existing Umami instance at `analytics.slpsoftware.nl` remains available and its script origin can be added to the CSP. | FR-16, FR-18. | Umami setup gains host work, as in the reference project. |
| ASM-06 | One Sentry project with environment tags is acceptable for both backend and frontend events of this CMS. | D-19. | Split into more projects; only configuration changes. | | ASM-06 | One Sentry project with environment tags is acceptable for both backend and frontend events of this CMS. | D-19. | Split into more projects; only configuration changes. |
| ASM-07 | Existing Gitea secrets for the Pi (`PI_MAIN_*` in the reference project) can be reused or replicated for this repository. | FR-02. | New secrets are created; documented in FR-23. | | ASM-07 | Existing Gitea secrets for the Pi (`PI_MAIN_*` in the reference project) can be reused or replicated for this repository. | FR-02. | New secrets are created; documented in FR-23. |
@@ -20,8 +20,9 @@ assumes already exists — `deploy-scp.yaml` (U6) never creates any of it.
### 1.1 Prerequisites ### 1.1 Prerequisites
- .NET 10 runtime installed on the Pi (ASM-03) — the publish is framework-dependent - .NET 10 runtime installed on the Pi (ASM-03) — the publish is framework-dependent
(`infrastructure-design.md` § 1), so the Pi needs the runtime, not the full SDK (`infrastructure-design.md` § 1), so the Pi needs the runtime, not the full SDK
- `sqlcmd` installed, for the backup script (§ 4) — e.g. `mssql-tools18` / `unixodbc` on Debian-based - `mariadb-client` (or `mariadb-dump`/`mysqldump` specifically) installed, for the backup script (§ 4)
Raspberry Pi OS — already present on most Raspberry Pi OS images that also run `mariadb-server`; install
`mariadb-client` explicitly if the dump tool isn't already there
### 1.2 Account Model (revised — `webadmin` is not the deploy account) ### 1.2 Account Model (revised — `webadmin` is not the deploy account)
@@ -106,7 +107,7 @@ below):
```ini ```ini
ASPNETCORE_ENVIRONMENT=Production ASPNETCORE_ENVIRONMENT=Production
ASPNETCORE_URLS=http://localhost:<port> ASPNETCORE_URLS=http://localhost:<port>
ConnectionStrings__DefaultConnection=Server=127.0.0.1,1433;User ID=<user>;Password=<password>;Database=SlpSoftware<Env>;TrustServerCertificate=True ConnectionStrings__DefaultConnection=Server=127.0.0.1;Port=3306;Database=SlpSoftware<Env>;Uid=<user>;Pwd=<password>
JwtSettings__Secret=<secure-long-random-secret> JwtSettings__Secret=<secure-long-random-secret>
JwtSettings__Issuer=SlpModularCms JwtSettings__Issuer=SlpModularCms
JwtSettings__Audience=SlpModularCmsPortal JwtSettings__Audience=SlpModularCmsPortal
@@ -213,14 +214,15 @@ touch ~/.config/slpsoftware-db-backup.env
chmod 600 ~/.config/slpsoftware-db-backup.env chmod 600 ~/.config/slpsoftware-db-backup.env
``` ```
```ini ```ini
DB_SERVER=127.0.0.1,1433 DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=SlpSoftwareProduction DB_NAME=SlpSoftwareProduction
DB_USER=<a-login-with-backup-database-permission> DB_USER=<a-login-with-just-SELECT-LOCK-TABLES-permission>
DB_PASSWORD=<password> DB_PASSWORD=<password>
``` ```
Kept **separate** from `shared/env` (§ 1.5) deliberately — the backup script needs its own Kept **separate** from `shared/env` (§ 1.5) deliberately — the backup script needs its own
credential, ideally scoped to just `BACKUP DATABASE` permission rather than the application's own credential, ideally scoped to just read access (`SELECT`, `LOCK TABLES` — everything `mariadb-dump`
data-access login. needs) rather than the application's own data-access login.
### 1.9 Gitea Actions Variables and Secrets ### 1.9 Gitea Actions Variables and Secrets
Set once, in this repository's Gitea Actions settings. This is exactly where the real path lives — Set once, in this repository's Gitea Actions settings. This is exactly where the real path lives —
@@ -286,22 +288,29 @@ if [[ ! -f "$CREDENTIALS_FILE" ]]; then
fi fi
# shellcheck source=/dev/null # shellcheck source=/dev/null
source "$CREDENTIALS_FILE" source "$CREDENTIALS_FILE"
: "${DB_SERVER:?}" "${DB_NAME:?}" "${DB_USER:?}" "${DB_PASSWORD:?}" : "${DB_HOST:?}" "${DB_PORT:?}" "${DB_NAME:?}" "${DB_USER:?}" "${DB_PASSWORD:?}"
BACKUP_DIR="$HOME/backups/slpsoftware/${ENVIRONMENT}" BACKUP_DIR="$HOME/backups/slpsoftware/${ENVIRONMENT}"
mkdir -p "$BACKUP_DIR" mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date -u +%Y%m%d%H%M%S) TIMESTAMP=$(date -u +%Y%m%d%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.bak" BACKUP_FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.sql.gz"
sqlcmd -S "$DB_SERVER" -U "$DB_USER" -P "$DB_PASSWORD" -C -Q \ # --single-transaction: consistent snapshot without locking the tables for the whole dump duration
"BACKUP DATABASE [$DB_NAME] TO DISK = N'$BACKUP_FILE' WITH INIT, COMPRESSION" # (InnoDB only — every table here is, since that's EF Core's MySQL-provider default).
mariadb-dump \
-h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" \
--single-transaction --routines --triggers \
"$DB_NAME" | gzip > "$BACKUP_FILE"
echo "Backup written to $BACKUP_FILE" echo "Backup written to $BACKUP_FILE"
# Retention: keep the 7 most recent backups for this environment # Retention: keep the 7 most recent backups for this environment
ls -1t "$BACKUP_DIR"/*.bak 2>/dev/null | tail -n +8 | xargs -r rm -f ls -1t "$BACKUP_DIR"/*.sql.gz 2>/dev/null | tail -n +8 | xargs -r rm -f
``` ```
`mariadb-dump` is MariaDB's own name for the tool (present since MariaDB 10.4-ish); if the host only
has the older `mysqldump` name, substitute it — same tool, same flags.
```bash ```bash
chmod +x ~/scripts/backup-slpmodularcms-db.sh chmod +x ~/scripts/backup-slpmodularcms-db.sh
``` ```
@@ -310,9 +319,13 @@ chmod +x ~/scripts/backup-slpmodularcms-db.sh
```bash ```bash
~/scripts/backup-slpmodularcms-db.sh production ~/scripts/backup-slpmodularcms-db.sh production
``` ```
Confirm a `.bak` file appears under `~/backups/slpsoftware/production/` and that `sqlcmd` didn't Confirm a `.sql.gz` file appears under `~/backups/slpsoftware/production/` and that the dump didn't
silently fail (the script uses `set -euo pipefail`, so a real SQL error does propagate as a non-zero silently fail (the script uses `set -euo pipefail`, so a real error does propagate as a non-zero
exit — which fails the calling `deploy-scp.yaml` step, correctly blocking the deploy). exit — which fails the calling `deploy-scp.yaml` step, correctly blocking the deploy). Worth a
one-time restore rehearsal too — an untested backup is not a verified one:
```bash
gunzip -c ~/backups/slpsoftware/production/<file>.sql.gz | mariadb -h 127.0.0.1 -u root -p <a-scratch-database>
```
## 5. Future: Switching Production to FTPS (Shared Hosting) ## 5. Future: Switching Production to FTPS (Shared Hosting)
@@ -336,9 +349,10 @@ than the transport:
`wwwroot/web/` persistence (FR-08, ASM-01) would need a **different** mechanism on such a host — `wwwroot/web/` persistence (FR-08, ASM-01) would need a **different** mechanism on such a host —
e.g. never touching a specific subfolder during upload, rather than linking a persistent directory e.g. never touching a specific subfolder during upload, rather than linking a persistent directory
outside a swapped release tree, since "outside the release tree" may not be an available concept outside a swapped release tree, since "outside the release tree" may not be an available concept
- **Database backup** — shared hosting frequently does not expose direct `sqlcmd`/SSH access at all; - **Database backup** — shared hosting frequently does not expose direct `mariadb-dump`/SSH access
the backup step in `deploy-scp.yaml` (§ 4 script) would need to become either a provider-specific at all; the backup step in `deploy-scp.yaml` (§ 4 script) would need to become either a
API call or a documented manual pre-production step (the FR-20 fallback U6 already designed for) provider-specific API call (e.g. a hosting-panel database backup feature) or a documented manual
pre-production step (the FR-20 fallback U6 already designed for)
### 5.3 What building `deploy-ftps.yaml` would actually require ### 5.3 What building `deploy-ftps.yaml` would actually require
1. A new reusable workflow implementing the **same five required inputs** 1. A new reusable workflow implementing the **same five required inputs**
@@ -46,8 +46,7 @@ A backup is taken before every **production** deploy (`operations/deployment/dep
§ 4, invoked by `deploy-scp.yaml` when `run_db_backup: true`). To restore: § 4, invoked by `deploy-scp.yaml` when `run_db_backup: true`). To restore:
```bash ```bash
sqlcmd -S <server> -U <user> -P <password> -C -Q \ gunzip -c <path-to-backup>.sql.gz | mariadb -h <server> -u <user> -p SlpSoftwareProduction
"RESTORE DATABASE [SlpModularCmsProduction] FROM DISK = N'<path-to-backup>.bak' WITH REPLACE"
``` ```
Restoring a database backup and rolling back the application release are **independent actions** Restoring a database backup and rolling back the application release are **independent actions**
@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SlpModularCmsSlave;Trusted_Connection=True;MultipleActiveResultSets=true" "DefaultConnection": "Server=127.0.0.1;Port=3306;Database=SlpModularCmsSlave;Uid=root;Pwd=<your-local-mariadb-password>"
}, },
"JwtSettings": { "JwtSettings": {
"Secret": "SuperSecretKeyForDevelopmentOnly_MustBeLongerThan32Bytes!", "Secret": "SuperSecretKeyForDevelopmentOnly_MustBeLongerThan32Bytes!",
@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1,1433;User ID=sa;Password=<your-local-sql-password>;Database=SlpModularCmsSlave;TrustServerCertificate=True;MultipleActiveResultSets=true" "DefaultConnection": "Server=127.0.0.1;Port=3306;Uid=root;Pwd=<your-local-mariadb-password>;Database=SlpModularCmsSlave"
}, },
"JwtSettings": { "JwtSettings": {
"Secret": "<your-local-secret-key-min-32-bytes>", "Secret": "<your-local-secret-key-min-32-bytes>",
@@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SlpModularCms;Trusted_Connection=True;MultipleActiveResultSets=true" "DefaultConnection": "Server=127.0.0.1;Port=3306;Database=SlpModularCms;Uid=root;Pwd=<your-local-mariadb-password>"
}, },
"JwtSettings": { "JwtSettings": {
"Secret": "SuperSecretKeyForDevelopmentOnly_MustBeLongerThan32Bytes!", "Secret": "SuperSecretKeyForDevelopmentOnly_MustBeLongerThan32Bytes!",
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=<production-db-host>;Database=SlpModularCms;User Id=<db-user>;Password=<db-password>;TrustServerCertificate=True" "DefaultConnection": "Server=<production-db-host>;Port=3306;Database=SlpModularCms;Uid=<db-user>;Pwd=<db-password>"
}, },
"JwtSettings": { "JwtSettings": {
"Secret": "<secure-long-random-secret-key-from-env>", "Secret": "<secure-long-random-secret-key-from-env>",
@@ -1,5 +1,5 @@
using FluentAssertions; using FluentAssertions;
using Microsoft.Data.SqlClient; using MySql.Data.MySqlClient;
using SlpModularCms.Core.Hosting; using SlpModularCms.Core.Hosting;
using Xunit; using Xunit;
@@ -47,9 +47,9 @@ public class DatabaseMigrationExtensionsTests
public static TheoryData<Exception> TransientFailures() => public static TheoryData<Exception> TransientFailures() =>
[ [
MakeSqlException(), MakeMySqlException(),
new TimeoutException("Connect Timeout expired."), new TimeoutException("Connect Timeout expired."),
new InvalidOperationException("wrapper", MakeSqlException()), new InvalidOperationException("wrapper", MakeMySqlException()),
]; ];
public static TheoryData<Exception> NonTransientFailures() => public static TheoryData<Exception> NonTransientFailures() =>
@@ -71,18 +71,18 @@ public class DatabaseMigrationExtensionsTests
} }
/// <summary> /// <summary>
/// <see cref="SqlException"/> has no public constructor, so one is produced through the /// <see cref="MySqlException"/> has no public constructor, so one is produced through the
/// framework's own factory path via reflection. /// framework's own factory path via reflection.
/// </summary> /// </summary>
private static Exception MakeSqlException() private static Exception MakeMySqlException()
{ {
try try
{ {
// Deliberately unreachable host and a very short timeout: this genuinely produces a // Deliberately unreachable host and a very short timeout: this genuinely produces a
// SqlException rather than a hand-built stand-in, so the classifier is tested against // MySqlException rather than a hand-built stand-in, so the classifier is tested against
// the real type it will encounter in production. // the real type it will encounter in production.
using var connection = new SqlConnection( using var connection = new MySqlConnection(
"Server=localhost,9;Database=none;User Id=sa;Password=none;Connect Timeout=1;TrustServerCertificate=True"); "Server=localhost;Port=9;Database=none;Uid=none;Pwd=none;Connection Timeout=1");
connection.Open(); connection.Open();
} }
catch (Exception ex) catch (Exception ex)
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Sentry; using Sentry;
@@ -108,7 +108,7 @@ public static class DatabaseMigrationExtensions
{ {
for (var current = exception; current is not null; current = current.InnerException) for (var current = exception; current is not null; current = current.InnerException)
{ {
if (current is SqlException or TimeoutException) if (current is MySqlException or TimeoutException)
{ {
return true; return true;
} }
@@ -0,0 +1,99 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
namespace SlpModularCms.Core.Hosting;
/// <summary>
/// Works around a confirmed bug in Oracle's <c>MySql.EntityFrameworkCore</c> provider (10.0.7)
/// against MariaDB: its <c>AcquireDatabaseLock</c> issues MariaDB's <c>GET_LOCK()</c> and casts
/// the result straight to <see cref="long"/>, but MariaDB returns <c>NULL</c> in a case real MySQL
/// Server apparently doesn't — producing an unconditional <see cref="InvalidCastException"/> that
/// blocks every migration attempt, reproduced against a real MariaDB instance during Operations.
/// </summary>
/// <remarks>
/// Oracle's own <c>MySQLHistoryRepository</c> is an internal type, so it cannot be subclassed
/// directly to override just the lock methods. This instead constructs a real instance of it via
/// reflection (its constructor is public even though the class itself is not) and forwards every
/// <see cref="IHistoryRepository"/> member to that instance, except the two lock methods — which
/// never reach the broken call at all.
///
/// Skipping the lock is acceptable here because migrations only ever run from one place at a
/// time: <c>MigrateCoreDatabase()</c> at startup, driven by the atomic-release deploy sequence,
/// which never runs two deploys concurrently against the same environment. A genuinely concurrent
/// multi-instance migration race is not a scenario this deployment model produces.
/// </remarks>
public sealed class NonLockingMySQLHistoryRepository : IHistoryRepository
{
private const string InnerTypeName = "MySql.EntityFrameworkCore.Migrations.Internal.MySQLHistoryRepository";
private readonly IHistoryRepository _inner;
public NonLockingMySQLHistoryRepository(HistoryRepositoryDependencies dependencies)
{
var innerType = typeof(MySQLDbContextOptionsExtensions).Assembly.GetType(InnerTypeName)
?? throw new InvalidOperationException(
$"{InnerTypeName} was not found. MySql.EntityFrameworkCore may have changed its internal " +
"layout — this workaround needs re-verifying against the new version.");
_inner = (IHistoryRepository)Activator.CreateInstance(innerType, dependencies)!;
}
public bool Exists() => _inner.Exists();
public Task<bool> ExistsAsync(CancellationToken cancellationToken = default) =>
_inner.ExistsAsync(cancellationToken);
public void Create() => _inner.Create();
public Task CreateAsync(CancellationToken cancellationToken = default) =>
_inner.CreateAsync(cancellationToken);
public bool CreateIfNotExists() => _inner.CreateIfNotExists();
public Task<bool> CreateIfNotExistsAsync(CancellationToken cancellationToken = default) =>
_inner.CreateIfNotExistsAsync(cancellationToken);
public IReadOnlyList<HistoryRow> GetAppliedMigrations() => _inner.GetAppliedMigrations();
public Task<IReadOnlyList<HistoryRow>> GetAppliedMigrationsAsync(CancellationToken cancellationToken = default) =>
_inner.GetAppliedMigrationsAsync(cancellationToken);
public LockReleaseBehavior LockReleaseBehavior => _inner.LockReleaseBehavior;
public string GetCreateScript() => _inner.GetCreateScript();
public string GetCreateIfNotExistsScript() => _inner.GetCreateIfNotExistsScript();
public string GetInsertScript(HistoryRow row) => _inner.GetInsertScript(row);
public string GetDeleteScript(string migrationId) => _inner.GetDeleteScript(migrationId);
public string GetBeginIfNotExistsScript(string migrationId) => _inner.GetBeginIfNotExistsScript(migrationId);
public string GetBeginIfExistsScript(string migrationId) => _inner.GetBeginIfExistsScript(migrationId);
public string GetEndIfScript() => _inner.GetEndIfScript();
// The actual workaround: never call into the inner repository's broken GET_LOCK path.
public IMigrationsDatabaseLock AcquireDatabaseLock() => new NoOpMigrationsDatabaseLock(this);
public Task<IMigrationsDatabaseLock> AcquireDatabaseLockAsync(CancellationToken cancellationToken = default) =>
Task.FromResult<IMigrationsDatabaseLock>(new NoOpMigrationsDatabaseLock(this));
private sealed class NoOpMigrationsDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock
{
public IHistoryRepository HistoryRepository { get; } = historyRepository;
public void Dispose()
{
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public IMigrationsDatabaseLock ReacquireIfNeeded(bool connectionOpened, bool? recreateIfInvalid) => this;
public Task<IMigrationsDatabaseLock> ReacquireIfNeededAsync(
bool connectionOpened, bool? recreateIfInvalid, CancellationToken cancellationToken = default) =>
Task.FromResult<IMigrationsDatabaseLock>(this);
}
}
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.RateLimiting; using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -33,7 +34,10 @@ public static class ServiceCollectionExtensions
{ {
// 1. Database // 1. Database
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"))); {
options.UseMySQL(configuration.GetConnectionString("DefaultConnection")!);
options.ReplaceService<IHistoryRepository, NonLockingMySQLHistoryRepository>();
});
// 2. Identity // 2. Identity
services.AddIdentityCore<ApplicationUser>(options => services.AddIdentityCore<ApplicationUser>(options =>
@@ -1,433 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Core.Data;
#nullable disable
namespace SlpModularCms.Core.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260619130625_AddsDisplayName")]
partial class AddsDisplayName
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("RoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("UserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("UserTokens", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.GlobalAvailabilityState", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("LastUpdatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Message")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("AvailabilityState", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.Invitation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset");
b.Property<bool>("IsAccepted")
.HasColumnType("bit");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Token")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("Token")
.IsUnique();
b.ToTable("Invitations");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ModuleName")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Permission")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.HasKey("UserId", "ModuleName", "Permission");
b.ToTable("ModulePermissions");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.RefreshToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedByIp")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset");
b.Property<bool>("IsRevoked")
.HasColumnType("bit");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Token")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("Token")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", "User")
.WithMany("ModulePermissions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.RefreshToken", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationUser", b =>
{
b.Navigation("ModulePermissions");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,31 +0,0 @@
// <auto-generated />
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SlpModularCms.Core.Migrations
{
/// <inheritdoc />
[ExcludeFromCodeCoverage]
public partial class AddsDisplayName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DisplayName",
table: "Users",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DisplayName",
table: "Users");
}
}
}
@@ -1,452 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Core.Data;
#nullable disable
namespace SlpModularCms.Core.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260727203036_AddDataProtectionKeys")]
partial class AddDataProtectionKeys
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("FriendlyName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Xml")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("DataProtectionKeys");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("RoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("UserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("UserTokens", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.GlobalAvailabilityState", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("LastUpdatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Message")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("AvailabilityState", (string)null);
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.Invitation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset");
b.Property<bool>("IsAccepted")
.HasColumnType("bit");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Token")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("Token")
.IsUnique();
b.ToTable("Invitations");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ModuleName")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Permission")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.HasKey("UserId", "ModuleName", "Permission");
b.ToTable("ModulePermissions");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.RefreshToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedByIp")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset");
b.Property<bool>("IsRevoked")
.HasColumnType("bit");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Token")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("Token")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", "User")
.WithMany("ModulePermissions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.RefreshToken", b =>
{
b.HasOne("SlpModularCms.Core.Identity.Entities.ApplicationUser", "User")
.WithMany("RefreshTokens")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ApplicationUser", b =>
{
b.Navigation("ModulePermissions");
b.Navigation("RefreshTokens");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,35 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SlpModularCms.Core.Migrations
{
/// <inheritdoc />
public partial class AddDataProtectionKeys : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DataProtectionKeys",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FriendlyName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Xml = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DataProtectionKeys");
}
}
}
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Core.Data; using SlpModularCms.Core.Data;
@@ -12,7 +11,7 @@ using SlpModularCms.Core.Data;
namespace SlpModularCms.Core.Migrations namespace SlpModularCms.Core.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("20260612191736_InitialCreate")] [Migration("20260729095344_InitialCreate")]
partial class InitialCreate partial class InitialCreate
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -21,9 +20,24 @@ namespace SlpModularCms.Core.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("FriendlyName")
.HasColumnType("longtext");
b.Property<string>("Xml")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("DataProtectionKeys");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{ {
@@ -31,16 +45,14 @@ namespace SlpModularCms.Core.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType") b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("ClaimValue") b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("RoleId") b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -55,16 +67,14 @@ namespace SlpModularCms.Core.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType") b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("ClaimValue") b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -76,16 +86,16 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{ {
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("ProviderKey") b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName") b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("LoginProvider", "ProviderKey"); b.HasKey("LoginProvider", "ProviderKey");
@@ -97,10 +107,10 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<Guid>("RoleId") b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("UserId", "RoleId"); b.HasKey("UserId", "RoleId");
@@ -112,16 +122,16 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("Value") b.Property<string>("Value")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name"); b.HasKey("UserId", "LoginProvider", "Name");
@@ -132,26 +142,25 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ConcurrencyStamp") b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken() .IsConcurrencyToken()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Name") b.Property<string>("Name")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("NormalizedName") b.Property<string>("NormalizedName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("NormalizedName") b.HasIndex("NormalizedName")
.IsUnique() .IsUnique()
.HasDatabaseName("RoleNameIndex") .HasDatabaseName("RoleNameIndex");
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null); b.ToTable("Roles", (string)null);
}); });
@@ -160,60 +169,63 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<int>("AccessFailedCount") b.Property<int>("AccessFailedCount")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("ConcurrencyStamp") b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken() .IsConcurrencyToken()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("DisplayName")
.HasColumnType("longtext");
b.Property<string>("Email") b.Property<string>("Email")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed") b.Property<bool>("EmailConfirmed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsActive") b.Property<bool>("IsActive")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled") b.Property<bool>("LockoutEnabled")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd") b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("NormalizedEmail") b.Property<string>("NormalizedEmail")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName") b.Property<string>("NormalizedUserName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("PasswordHash") b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("PhoneNumber") b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed") b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp") b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled") b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("UserName") b.Property<string>("UserName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
@@ -222,8 +234,7 @@ namespace SlpModularCms.Core.Migrations
b.HasIndex("NormalizedUserName") b.HasIndex("NormalizedUserName")
.IsUnique() .IsUnique()
.HasDatabaseName("UserNameIndex") .HasDatabaseName("UserNameIndex");
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null); b.ToTable("Users", (string)null);
}); });
@@ -232,19 +243,19 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("LastUpdatedAt") b.Property<DateTimeOffset>("LastUpdatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("UpdatedBy") b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
@@ -255,30 +266,30 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Email") b.Property<string>("Email")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<DateTimeOffset>("ExpiryDate") b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<bool>("IsAccepted") b.Property<bool>("IsAccepted")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("Role") b.Property<string>("Role")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Token") b.Property<string>("Token")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
@@ -291,15 +302,15 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b => modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ModuleName") b.Property<string>("ModuleName")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("varchar(128)");
b.Property<string>("Permission") b.Property<string>("Permission")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("varchar(128)");
b.HasKey("UserId", "ModuleName", "Permission"); b.HasKey("UserId", "ModuleName", "Permission");
@@ -310,30 +321,30 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("CreatedByIp") b.Property<string>("CreatedByIp")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<DateTimeOffset>("ExpiryDate") b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<bool>("IsRevoked") b.Property<bool>("IsRevoked")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsUsed") b.Property<bool>("IsUsed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("Token") b.Property<string>("Token")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -1,101 +1,122 @@
// <auto-generated /> using System;
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using MySql.EntityFrameworkCore.Metadata;
#nullable disable #nullable disable
namespace SlpModularCms.Core.Migrations namespace SlpModularCms.Core.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
[ExcludeFromCodeCoverage]
public partial class InitialCreate : Migration public partial class InitialCreate : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AlterDatabase()
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "AvailabilityState", name: "AvailabilityState",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false), Status = table.Column<int>(type: "int", nullable: false),
Message = table.Column<string>(type: "nvarchar(max)", nullable: true), Message = table.Column<string>(type: "longtext", nullable: true),
LastUpdatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), LastUpdatedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
UpdatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true) UpdatedBy = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_AvailabilityState", x => x.Id); table.PrimaryKey("PK_AvailabilityState", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "DataProtectionKeys",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
FriendlyName = table.Column<string>(type: "longtext", nullable: true),
Xml = table.Column<string>(type: "longtext", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Invitations", name: "Invitations",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: false),
Role = table.Column<string>(type: "nvarchar(max)", nullable: false), Role = table.Column<string>(type: "longtext", nullable: false),
Token = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Token = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: false),
ExpiryDate = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), ExpiryDate = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
IsAccepted = table.Column<bool>(type: "bit", nullable: false), IsAccepted = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false) CreatedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_Invitations", x => x.Id); table.PrimaryKey("PK_Invitations", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Roles", name: "Roles",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), Name = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), NormalizedName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true) ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_Roles", x => x.Id); table.PrimaryKey("PK_Roles", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Users", name: "Users",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false), IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), CreatedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), DisplayName = table.Column<string>(type: "longtext", nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), UserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), NormalizedUserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false), NormalizedEmail = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true), EmailConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true), PasswordHash = table.Column<string>(type: "longtext", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true), SecurityStamp = table.Column<string>(type: "longtext", nullable: true),
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true), ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false), PhoneNumber = table.Column<string>(type: "longtext", nullable: true),
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false), PhoneNumberConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true), TwoFactorEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false), LockoutEnd = table.Column<DateTimeOffset>(type: "datetime", nullable: true),
LockoutEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false) AccessFailedCount = table.Column<int>(type: "int", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_Users", x => x.Id); table.PrimaryKey("PK_Users", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "RoleClaims", name: "RoleClaims",
columns: table => new columns: table => new
{ {
Id = table.Column<int>(type: "int", nullable: false) Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"), .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), RoleId = table.Column<Guid>(type: "char(36)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true), ClaimType = table.Column<string>(type: "longtext", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true) ClaimValue = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
@@ -106,15 +127,16 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Roles", principalTable: "Roles",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "ModulePermissions", name: "ModulePermissions",
columns: table => new columns: table => new
{ {
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), UserId = table.Column<Guid>(type: "char(36)", nullable: false),
ModuleName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false), ModuleName = table.Column<string>(type: "varchar(128)", maxLength: 128, nullable: false),
Permission = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false) Permission = table.Column<string>(type: "varchar(128)", maxLength: 128, nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -125,20 +147,21 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "RefreshTokens", name: "RefreshTokens",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
Token = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Token = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), UserId = table.Column<Guid>(type: "char(36)", nullable: false),
ExpiryDate = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), ExpiryDate = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
IsUsed = table.Column<bool>(type: "bit", nullable: false), IsUsed = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsRevoked = table.Column<bool>(type: "bit", nullable: false), IsRevoked = table.Column<bool>(type: "tinyint(1)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), CreatedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
CreatedByIp = table.Column<string>(type: "nvarchar(max)", nullable: true) CreatedByIp = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
@@ -149,17 +172,18 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "UserClaims", name: "UserClaims",
columns: table => new columns: table => new
{ {
Id = table.Column<int>(type: "int", nullable: false) Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"), .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), UserId = table.Column<Guid>(type: "char(36)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true), ClaimType = table.Column<string>(type: "longtext", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true) ClaimValue = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
@@ -170,16 +194,17 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "UserLogins", name: "UserLogins",
columns: table => new columns: table => new
{ {
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false), LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false),
ProviderKey = table.Column<string>(type: "nvarchar(450)", nullable: false), ProviderKey = table.Column<string>(type: "varchar(255)", nullable: false),
ProviderDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true), ProviderDisplayName = table.Column<string>(type: "longtext", nullable: true),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false) UserId = table.Column<Guid>(type: "char(36)", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -190,14 +215,15 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "UserRoles", name: "UserRoles",
columns: table => new columns: table => new
{ {
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), UserId = table.Column<Guid>(type: "char(36)", nullable: false),
RoleId = table.Column<Guid>(type: "uniqueidentifier", nullable: false) RoleId = table.Column<Guid>(type: "char(36)", nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@@ -214,16 +240,17 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "UserTokens", name: "UserTokens",
columns: table => new columns: table => new
{ {
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), UserId = table.Column<Guid>(type: "char(36)", nullable: false),
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false), LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false),
Name = table.Column<string>(type: "nvarchar(450)", nullable: false), Name = table.Column<string>(type: "varchar(255)", nullable: false),
Value = table.Column<string>(type: "nvarchar(max)", nullable: true) Value = table.Column<string>(type: "longtext", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
@@ -234,7 +261,8 @@ namespace SlpModularCms.Core.Migrations
principalTable: "Users", principalTable: "Users",
principalColumn: "Id", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Invitations_Token", name: "IX_Invitations_Token",
@@ -262,8 +290,7 @@ namespace SlpModularCms.Core.Migrations
name: "RoleNameIndex", name: "RoleNameIndex",
table: "Roles", table: "Roles",
column: "NormalizedName", column: "NormalizedName",
unique: true, unique: true);
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_UserClaims_UserId", name: "IX_UserClaims_UserId",
@@ -289,8 +316,7 @@ namespace SlpModularCms.Core.Migrations
name: "UserNameIndex", name: "UserNameIndex",
table: "Users", table: "Users",
column: "NormalizedUserName", column: "NormalizedUserName",
unique: true, unique: true);
filter: "[NormalizedUserName] IS NOT NULL");
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -299,6 +325,9 @@ namespace SlpModularCms.Core.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "AvailabilityState"); name: "AvailabilityState");
migrationBuilder.DropTable(
name: "DataProtectionKeys");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Invitations"); name: "Invitations");
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Core.Data; using SlpModularCms.Core.Data;
@@ -18,9 +17,7 @@ namespace SlpModularCms.Core.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{ {
@@ -28,13 +25,11 @@ namespace SlpModularCms.Core.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("FriendlyName") b.Property<string>("FriendlyName")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Xml") b.Property<string>("Xml")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
@@ -47,16 +42,14 @@ namespace SlpModularCms.Core.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType") b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("ClaimValue") b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("RoleId") b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -71,16 +64,14 @@ namespace SlpModularCms.Core.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType") b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("ClaimValue") b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -92,16 +83,16 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{ {
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("ProviderKey") b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName") b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("LoginProvider", "ProviderKey"); b.HasKey("LoginProvider", "ProviderKey");
@@ -113,10 +104,10 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<Guid>("RoleId") b.Property<Guid>("RoleId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("UserId", "RoleId"); b.HasKey("UserId", "RoleId");
@@ -128,16 +119,16 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("nvarchar(450)"); .HasColumnType("varchar(255)");
b.Property<string>("Value") b.Property<string>("Value")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name"); b.HasKey("UserId", "LoginProvider", "Name");
@@ -148,26 +139,25 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ConcurrencyStamp") b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken() .IsConcurrencyToken()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Name") b.Property<string>("Name")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("NormalizedName") b.Property<string>("NormalizedName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("NormalizedName") b.HasIndex("NormalizedName")
.IsUnique() .IsUnique()
.HasDatabaseName("RoleNameIndex") .HasDatabaseName("RoleNameIndex");
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null); b.ToTable("Roles", (string)null);
}); });
@@ -176,63 +166,63 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<int>("AccessFailedCount") b.Property<int>("AccessFailedCount")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("ConcurrencyStamp") b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken() .IsConcurrencyToken()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("DisplayName") b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Email") b.Property<string>("Email")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed") b.Property<bool>("EmailConfirmed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsActive") b.Property<bool>("IsActive")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled") b.Property<bool>("LockoutEnabled")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd") b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("NormalizedEmail") b.Property<string>("NormalizedEmail")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName") b.Property<string>("NormalizedUserName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<string>("PasswordHash") b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("PhoneNumber") b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed") b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp") b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled") b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("UserName") b.Property<string>("UserName")
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
@@ -241,8 +231,7 @@ namespace SlpModularCms.Core.Migrations
b.HasIndex("NormalizedUserName") b.HasIndex("NormalizedUserName")
.IsUnique() .IsUnique()
.HasDatabaseName("UserNameIndex") .HasDatabaseName("UserNameIndex");
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null); b.ToTable("Users", (string)null);
}); });
@@ -251,19 +240,19 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("LastUpdatedAt") b.Property<DateTimeOffset>("LastUpdatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("UpdatedBy") b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
@@ -274,30 +263,30 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Email") b.Property<string>("Email")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<DateTimeOffset>("ExpiryDate") b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<bool>("IsAccepted") b.Property<bool>("IsAccepted")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("Role") b.Property<string>("Role")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<string>("Token") b.Property<string>("Token")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.HasKey("Id"); b.HasKey("Id");
@@ -310,15 +299,15 @@ namespace SlpModularCms.Core.Migrations
modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b => modelBuilder.Entity("SlpModularCms.Core.Identity.Entities.ModulePermission", b =>
{ {
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ModuleName") b.Property<string>("ModuleName")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("varchar(128)");
b.Property<string>("Permission") b.Property<string>("Permission")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("varchar(128)");
b.HasKey("UserId", "ModuleName", "Permission"); b.HasKey("UserId", "ModuleName", "Permission");
@@ -329,30 +318,30 @@ namespace SlpModularCms.Core.Migrations
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<DateTimeOffset>("CreatedAt") b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("CreatedByIp") b.Property<string>("CreatedByIp")
.HasColumnType("nvarchar(max)"); .HasColumnType("longtext");
b.Property<DateTimeOffset>("ExpiryDate") b.Property<DateTimeOffset>("ExpiryDate")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<bool>("IsRevoked") b.Property<bool>("IsRevoked")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsUsed") b.Property<bool>("IsUsed")
.HasColumnType("bit"); .HasColumnType("tinyint(1)");
b.Property<string>("Token") b.Property<string>("Token")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("varchar(256)");
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
@@ -22,7 +22,22 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.9" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.9" /> <!--
MariaDB/MySQL provider (Operations — the target Pi only has MariaDB, and MySQL Server has no
ARM64 build at all). Pomelo, not Oracle's official MySql.EntityFrameworkCore: Oracle's
provider has a confirmed MariaDB-incompatibility bug in its migration-lock acquisition
(AcquireDatabaseLock throws InvalidCastException — MariaDB's GET_LOCK() returns something
Oracle's code doesn't expect), reproduced against a real MariaDB instance. Pomelo has
first-class MariaDB support and worked cleanly on the same database.
Pomelo 9.0.0's own dependency range caps at EF Core 9.x (NU1608 warning at restore — no EF
Core 10 release exists yet), but per the Pomelo maintainers
(https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/2017) Pomelo 9 /
EF Core 9 packages are compatible with a net10.0 TargetFramework as long as no new EF Core 10
APIs are used — confirmed here empirically: restore/build/test/migrations all succeed with
Microsoft.AspNetCore.Identity.EntityFrameworkCore and
Microsoft.AspNetCore.DataProtection.EntityFrameworkCore left at 10.0.9.
-->
<PackageReference Include="MySql.EntityFrameworkCore" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
@@ -1,9 +1,11 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using SlpModularCms.Core.Availability; using SlpModularCms.Core.Availability;
using SlpModularCms.Core.Hosting;
using SlpModularCms.Core.Modules; using SlpModularCms.Core.Modules;
using SlpModularCms.Modules.Availability.BackgroundServices; using SlpModularCms.Modules.Availability.BackgroundServices;
using SlpModularCms.Modules.Availability.Config; using SlpModularCms.Modules.Availability.Config;
@@ -28,7 +30,8 @@ public class AvailabilityModule : IModule
services.AddDbContext<AvailabilityDbContext>((serviceProvider, options) => services.AddDbContext<AvailabilityDbContext>((serviceProvider, options) =>
{ {
var configuration = serviceProvider.GetRequiredService<IConfiguration>(); var configuration = serviceProvider.GetRequiredService<IConfiguration>();
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); options.UseMySQL(configuration.GetConnectionString("DefaultConnection")!);
options.ReplaceService<IHistoryRepository, NonLockingMySQLHistoryRepository>();
}); });
// Data Protection is configured once by the host (AddCmsDataProtection), NOT here. // Data Protection is configured once by the host (AddCmsDataProtection), NOT here.
@@ -1,60 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Modules.Availability.Data;
#nullable disable
namespace SlpModularCms.Modules.Availability.Migrations
{
[DbContext(typeof(AvailabilityDbContext))]
[Migration("20260704142458_AddLastPolledAtToMasterRegistration")]
partial class AddLastPolledAtToMasterRegistration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("SlpModularCms.Modules.Availability.Data.Entities.MasterRegistration", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ApiKey")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<DateTimeOffset?>("LastContactedAt")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset?>("LastPolledAt")
.HasColumnType("datetimeoffset");
b.Property<string>("MasterUrl")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTimeOffset>("RegisteredAt")
.HasColumnType("datetimeoffset");
b.HasKey("Id");
b.ToTable("AvailabilityMasterRegistrations", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -1,29 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SlpModularCms.Modules.Availability.Migrations
{
/// <inheritdoc />
public partial class AddLastPolledAtToMasterRegistration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTimeOffset>(
name: "LastPolledAt",
table: "AvailabilityMasterRegistrations",
type: "datetimeoffset",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LastPolledAt",
table: "AvailabilityMasterRegistrations");
}
}
}
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Modules.Availability.Data; using SlpModularCms.Modules.Availability.Data;
@@ -12,7 +11,7 @@ using SlpModularCms.Modules.Availability.Data;
namespace SlpModularCms.Modules.Availability.Migrations namespace SlpModularCms.Modules.Availability.Migrations
{ {
[DbContext(typeof(AvailabilityDbContext))] [DbContext(typeof(AvailabilityDbContext))]
[Migration("20260701200414_InitialCreate")] [Migration("20260729095354_InitialCreate")]
partial class InitialCreate partial class InitialCreate
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -21,31 +20,32 @@ namespace SlpModularCms.Modules.Availability.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("SlpModularCms.Modules.Availability.Data.Entities.MasterRegistration", b => modelBuilder.Entity("SlpModularCms.Modules.Availability.Data.Entities.MasterRegistration", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ApiKey") b.Property<string>("ApiKey")
.IsRequired() .IsRequired()
.HasMaxLength(2000) .HasMaxLength(2000)
.HasColumnType("nvarchar(2000)"); .HasColumnType("varchar(2000)");
b.Property<DateTimeOffset?>("LastContactedAt") b.Property<DateTimeOffset?>("LastContactedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastPolledAt")
.HasColumnType("datetime");
b.Property<string>("MasterUrl") b.Property<string>("MasterUrl")
.IsRequired() .IsRequired()
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.Property<DateTimeOffset>("RegisteredAt") b.Property<DateTimeOffset>("RegisteredAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.HasKey("Id"); b.HasKey("Id");
@@ -11,20 +11,25 @@ namespace SlpModularCms.Modules.Availability.Migrations
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AlterDatabase()
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "AvailabilityMasterRegistrations", name: "AvailabilityMasterRegistrations",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
MasterUrl = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false), MasterUrl = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: false),
ApiKey = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false), ApiKey = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false),
RegisteredAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false), RegisteredAt = table.Column<DateTimeOffset>(type: "datetime", nullable: false),
LastContactedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true) LastContactedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: true),
LastPolledAt = table.Column<DateTimeOffset>(type: "datetime", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_AvailabilityMasterRegistrations", x => x.Id); table.PrimaryKey("PK_AvailabilityMasterRegistrations", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Modules.Availability.Data; using SlpModularCms.Modules.Availability.Data;
@@ -18,34 +17,32 @@ namespace SlpModularCms.Modules.Availability.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("SlpModularCms.Modules.Availability.Data.Entities.MasterRegistration", b => modelBuilder.Entity("SlpModularCms.Modules.Availability.Data.Entities.MasterRegistration", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ApiKey") b.Property<string>("ApiKey")
.IsRequired() .IsRequired()
.HasMaxLength(2000) .HasMaxLength(2000)
.HasColumnType("nvarchar(2000)"); .HasColumnType("varchar(2000)");
b.Property<DateTimeOffset?>("LastContactedAt") b.Property<DateTimeOffset?>("LastContactedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastPolledAt") b.Property<DateTimeOffset?>("LastPolledAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("MasterUrl") b.Property<string>("MasterUrl")
.IsRequired() .IsRequired()
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.Property<DateTimeOffset>("RegisteredAt") b.Property<DateTimeOffset>("RegisteredAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.HasKey("Id"); b.HasKey("Id");
@@ -1,10 +1,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience; using Microsoft.Extensions.Http.Resilience;
using Polly; using Polly;
using SlpModularCms.Core.Hosting;
using SlpModularCms.Core.Modules; using SlpModularCms.Core.Modules;
using SlpModularCms.Modules.Master.BackgroundServices; using SlpModularCms.Modules.Master.BackgroundServices;
using SlpModularCms.Modules.Master.Data; using SlpModularCms.Modules.Master.Data;
@@ -33,7 +35,8 @@ public class MasterModule : IModule
services.AddDbContext<MasterDbContext>((serviceProvider, options) => services.AddDbContext<MasterDbContext>((serviceProvider, options) =>
{ {
var config = serviceProvider.GetRequiredService<IConfiguration>(); var config = serviceProvider.GetRequiredService<IConfiguration>();
options.UseSqlServer(config.GetConnectionString("DefaultConnection")); options.UseMySQL(config.GetConnectionString("DefaultConnection")!);
options.ReplaceService<IHistoryRepository, NonLockingMySQLHistoryRepository>();
}); });
services.AddScoped<ICmsInstanceRepository, CmsInstanceRepository>(); services.AddScoped<ICmsInstanceRepository, CmsInstanceRepository>();
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Modules.Master.Data; using SlpModularCms.Modules.Master.Data;
@@ -12,7 +11,7 @@ using SlpModularCms.Modules.Master.Data;
namespace SlpModularCms.Modules.Master.Migrations namespace SlpModularCms.Modules.Master.Migrations
{ {
[DbContext(typeof(MasterDbContext))] [DbContext(typeof(MasterDbContext))]
[Migration("20260630210103_InitialCreate")] [Migration("20260729095359_InitialCreate")]
partial class InitialCreate partial class InitialCreate
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -21,38 +20,36 @@ namespace SlpModularCms.Modules.Master.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("SlpModularCms.Modules.Master.Data.Entities.CmsInstance", b => modelBuilder.Entity("SlpModularCms.Modules.Master.Data.Entities.CmsInstance", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ApiKey") b.Property<string>("ApiKey")
.IsRequired() .IsRequired()
.HasMaxLength(1000) .HasMaxLength(1000)
.HasColumnType("nvarchar(1000)"); .HasColumnType("varchar(1000)");
b.Property<string>("DisableMessage") b.Property<string>("DisableMessage")
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.Property<DateTimeOffset?>("LastContactedAt") b.Property<DateTimeOffset?>("LastContactedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastIntegrityCheckFailedAt") b.Property<DateTimeOffset?>("LastIntegrityCheckFailedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastStatusPushedAt") b.Property<DateTimeOffset?>("LastStatusPushedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("nvarchar(200)"); .HasColumnType("varchar(200)");
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
@@ -60,7 +57,7 @@ namespace SlpModularCms.Modules.Master.Migrations
b.Property<string>("Url") b.Property<string>("Url")
.IsRequired() .IsRequired()
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.HasKey("Id"); b.HasKey("Id");
@@ -11,24 +11,28 @@ namespace SlpModularCms.Modules.Master.Migrations
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AlterDatabase()
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "MasterCmsInstances", name: "MasterCmsInstances",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "char(36)", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), Name = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false),
Url = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false), Url = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: false),
ApiKey = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false), ApiKey = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false),
Status = table.Column<int>(type: "int", nullable: false), Status = table.Column<int>(type: "int", nullable: false),
DisableMessage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true), DisableMessage = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
LastContactedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true), LastContactedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: true),
LastStatusPushedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true), LastStatusPushedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: true),
LastIntegrityCheckFailedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true) LastIntegrityCheckFailedAt = table.Column<DateTimeOffset>(type: "datetime", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_MasterCmsInstances", x => x.Id); table.PrimaryKey("PK_MasterCmsInstances", x => x.Id);
}); })
.Annotation("MySQL:Charset", "utf8mb4");
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -2,7 +2,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SlpModularCms.Modules.Master.Data; using SlpModularCms.Modules.Master.Data;
@@ -18,38 +17,36 @@ namespace SlpModularCms.Modules.Master.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 64);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("SlpModularCms.Modules.Master.Data.Entities.CmsInstance", b => modelBuilder.Entity("SlpModularCms.Modules.Master.Data.Entities.CmsInstance", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("char(36)");
b.Property<string>("ApiKey") b.Property<string>("ApiKey")
.IsRequired() .IsRequired()
.HasMaxLength(1000) .HasMaxLength(1000)
.HasColumnType("nvarchar(1000)"); .HasColumnType("varchar(1000)");
b.Property<string>("DisableMessage") b.Property<string>("DisableMessage")
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.Property<DateTimeOffset?>("LastContactedAt") b.Property<DateTimeOffset?>("LastContactedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastIntegrityCheckFailedAt") b.Property<DateTimeOffset?>("LastIntegrityCheckFailedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<DateTimeOffset?>("LastStatusPushedAt") b.Property<DateTimeOffset?>("LastStatusPushedAt")
.HasColumnType("datetimeoffset"); .HasColumnType("datetime");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(200) .HasMaxLength(200)
.HasColumnType("nvarchar(200)"); .HasColumnType("varchar(200)");
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
@@ -57,7 +54,7 @@ namespace SlpModularCms.Modules.Master.Migrations
b.Property<string>("Url") b.Property<string>("Url")
.IsRequired() .IsRequired()
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("nvarchar(500)"); .HasColumnType("varchar(500)");
b.HasKey("Id"); b.HasKey("Id");