262 lines
6.5 KiB
Markdown
262 lines
6.5 KiB
Markdown
# Multi-Tenancy Deployment Guide
|
|
|
|
## Pre-Deployment Checklist
|
|
|
|
- [ ] Code review completed
|
|
- [ ] All tests passing
|
|
- [ ] Database backup created
|
|
- [ ] Rollback plan prepared
|
|
- [ ] Downtime window scheduled (if needed)
|
|
|
|
## Deployment Steps
|
|
|
|
### Step 1: Backup Database
|
|
|
|
```bash
|
|
# SQL Server backup example
|
|
BACKUP DATABASE PowderCoatingDb
|
|
TO DISK = 'C:\Backups\PowderCoatingDb_PreMultiTenancy.bak'
|
|
WITH FORMAT, COMPRESSION;
|
|
```
|
|
|
|
### Step 2: Build Solution
|
|
|
|
```bash
|
|
cd Y:\PCC\PowderCoatingApp
|
|
dotnet build
|
|
```
|
|
|
|
Ensure no build errors.
|
|
|
|
### Step 3: Review Migration
|
|
|
|
Check the migration file to understand what will happen:
|
|
```bash
|
|
# View the migration
|
|
cat src/PowderCoating.Infrastructure/Migrations/20260205220415_AddMultiTenancy.cs
|
|
```
|
|
|
|
The migration will:
|
|
- Create `Companies` table
|
|
- Add `CompanyId` columns to all entities
|
|
- Add `CompanyRole` to `AspNetUsers`
|
|
- Create foreign keys and indexes
|
|
|
|
### Step 4: Apply Migration
|
|
|
|
```bash
|
|
cd src/PowderCoating.Web
|
|
dotnet ef database update --project ../PowderCoating.Infrastructure
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
Applying migration '20260205220415_AddMultiTenancy'.
|
|
Done.
|
|
```
|
|
|
|
### Step 5: Run Seed Data
|
|
|
|
The seed data will run automatically on application startup and will:
|
|
- Create default "Demo Company"
|
|
- Create SuperAdmin user
|
|
- Create CompanyAdmin user
|
|
- Create Manager user
|
|
|
|
Start the application:
|
|
```bash
|
|
dotnet run --project src/PowderCoating.Web
|
|
```
|
|
|
|
Watch the logs for:
|
|
```
|
|
Company 'Demo Company' created
|
|
User 'superadmin@powdercoating.com' created
|
|
User 'admin@demo.com' created
|
|
User 'manager@demo.com' created
|
|
```
|
|
|
|
### Step 6: Verify Migration Success
|
|
|
|
Connect to the database and verify:
|
|
|
|
```sql
|
|
-- Check Companies table exists
|
|
SELECT * FROM Companies;
|
|
|
|
-- Check CompanyId added to entities
|
|
SELECT TOP 5 Id, CompanyId FROM Customers;
|
|
SELECT TOP 5 Id, CompanyId FROM Jobs;
|
|
|
|
-- Check ApplicationUser has CompanyId
|
|
SELECT TOP 5 Id, Email, CompanyId, CompanyRole FROM AspNetUsers;
|
|
|
|
-- Verify foreign keys
|
|
SELECT
|
|
fk.name AS ForeignKeyName,
|
|
OBJECT_NAME(fk.parent_object_id) AS TableName,
|
|
COL_NAME(fkc.parent_object_id, fkc.parent_column_id) AS ColumnName,
|
|
OBJECT_NAME (fk.referenced_object_id) AS ReferencedTableName
|
|
FROM
|
|
sys.foreign_keys AS fk
|
|
INNER JOIN sys.foreign_key_columns AS fkc
|
|
ON fk.object_id = fkc.constraint_object_id
|
|
WHERE
|
|
OBJECT_NAME(fk.referenced_object_id) = 'Companies';
|
|
```
|
|
|
|
### Step 7: Test Login
|
|
|
|
Test each user account:
|
|
|
|
1. **SuperAdmin Login**
|
|
- URL: https://localhost:5001/Identity/Account/Login
|
|
- Email: superadmin@powdercoating.com
|
|
- Password: SuperAdmin123!
|
|
- Should see: "Companies" menu in Platform Management
|
|
|
|
2. **Company Admin Login**
|
|
- URL: https://localhost:5001/Identity/Account/Login
|
|
- Email: admin@demo.com
|
|
- Password: CompanyAdmin123!
|
|
- Should see: "Manage Users" menu in Company Settings
|
|
- Should see: "Demo Company" badge in header
|
|
|
|
3. **Manager Login**
|
|
- URL: https://localhost:5001/Identity/Account/Login
|
|
- Email: manager@demo.com
|
|
- Password: Manager123!
|
|
- Should see: "Demo Company" badge in header
|
|
- Should NOT see: "Manage Users" menu
|
|
|
|
### Step 8: Verify Data Isolation
|
|
|
|
1. As **SuperAdmin**, create a second company:
|
|
- Navigate to Companies → Create New Company
|
|
- Company Name: "Test Company"
|
|
- Create admin user for Test Company
|
|
|
|
2. Login as **Test Company Admin**
|
|
- Verify you only see Test Company data
|
|
- Create a test customer
|
|
|
|
3. Login as **Demo Company Admin**
|
|
- Verify you CANNOT see Test Company customer
|
|
- Verify you only see Demo Company data
|
|
|
|
4. Login as **SuperAdmin**
|
|
- Navigate to Customers
|
|
- Should see customers from BOTH companies
|
|
|
|
## Post-Deployment Verification
|
|
|
|
### Functional Tests
|
|
|
|
- [ ] SuperAdmin can create new companies
|
|
- [ ] SuperAdmin can see all companies' data
|
|
- [ ] Company Admin can create users in their company
|
|
- [ ] Company Admin cannot see other companies' data
|
|
- [ ] Users can only see data from their own company
|
|
- [ ] New entities automatically get CompanyId assigned
|
|
- [ ] Query filters work correctly
|
|
|
|
### Performance Tests
|
|
|
|
- [ ] Page load times are acceptable
|
|
- [ ] No N+1 query issues
|
|
- [ ] Indexes are being used (check query plans)
|
|
|
|
### Security Tests
|
|
|
|
- [ ] Cannot access other company's data by changing IDs in URL
|
|
- [ ] Authorization policies enforce correctly
|
|
- [ ] Query filters cannot be bypassed by regular users
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Migration Fails
|
|
|
|
**Error: Foreign key constraint conflicts**
|
|
|
|
This means existing data has CompanyId=0 which doesn't exist.
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- Check if default company exists
|
|
SELECT * FROM Companies WHERE Id = 1;
|
|
|
|
-- If no company exists, seed data didn't run
|
|
-- Run application to trigger seed, OR manually:
|
|
INSERT INTO Companies (CompanyName, CompanyCode, PrimaryContactName, PrimaryContactEmail,
|
|
IsActive, SubscriptionStartDate, CreatedAt, CompanyId)
|
|
VALUES ('Demo Company', 'DEMO', 'Admin', 'admin@demo.com',
|
|
1, GETUTCDATE(), GETUTCDATE(), 0);
|
|
|
|
-- Update CompanyId to self-reference
|
|
UPDATE Companies SET CompanyId = Id WHERE Id = 1;
|
|
|
|
-- Update existing data
|
|
UPDATE Customers SET CompanyId = 1 WHERE CompanyId = 0;
|
|
UPDATE Jobs SET CompanyId = 1 WHERE CompanyId = 0;
|
|
-- ... repeat for all entities
|
|
```
|
|
|
|
### Issue: Users Can't Login
|
|
|
|
**Error: "Unable to determine your company"**
|
|
|
|
This means the user's CompanyId is not set or CompanyId claim is missing.
|
|
|
|
**Solution:**
|
|
```sql
|
|
-- Check user's CompanyId
|
|
SELECT Id, Email, CompanyId FROM AspNetUsers WHERE Email = 'user@example.com';
|
|
|
|
-- Update if needed
|
|
UPDATE AspNetUsers SET CompanyId = 1 WHERE CompanyId = 0 OR CompanyId IS NULL;
|
|
```
|
|
|
|
Then ensure user re-logs in to get fresh claims.
|
|
|
|
### Issue: Seeing Other Company's Data
|
|
|
|
This means query filters aren't working.
|
|
|
|
**Check:**
|
|
1. Is `ITenantContext` registered in DI?
|
|
2. Is `ApplicationDbContext` receiving `IHttpContextAccessor` and `IServiceProvider`?
|
|
3. Are query filters being applied in `OnModelCreating`?
|
|
4. Is the user authenticated and has CompanyId claim?
|
|
|
|
## Rollback Procedure
|
|
|
|
If critical issues occur:
|
|
|
|
```bash
|
|
# Rollback migration
|
|
cd src/PowderCoating.Web
|
|
dotnet ef database update 20260205163837_InitialCreate --project ../PowderCoating.Infrastructure
|
|
|
|
# Restore database backup
|
|
RESTORE DATABASE PowderCoatingDb
|
|
FROM DISK = 'C:\Backups\PowderCoatingDb_PreMultiTenancy.bak'
|
|
WITH REPLACE;
|
|
|
|
# Revert code changes
|
|
git revert <commit-hash>
|
|
```
|
|
|
|
## Support Contacts
|
|
|
|
- Lead Developer: [Your Name]
|
|
- Database Admin: [DBA Name]
|
|
- DevOps: [DevOps Contact]
|
|
|
|
## Post-Deployment Tasks
|
|
|
|
- [ ] Monitor application logs for errors
|
|
- [ ] Monitor database performance
|
|
- [ ] Update documentation
|
|
- [ ] Train users on new multi-tenancy features
|
|
- [ ] Schedule follow-up review (1 week)
|