Add record access strategy

This commit is contained in:
Francisco Gaona
2026-01-05 07:48:22 +01:00
parent 838a010fb2
commit 16907aadf8
97 changed files with 11350 additions and 208 deletions

View File

@@ -1,8 +1,53 @@
# Tenant Migration Scripts
# Tenant Migration & Admin Scripts
This directory contains scripts for managing database migrations across all tenants in the multi-tenant platform.
This directory contains scripts for managing database migrations across all tenants and creating admin users in the multi-tenant platform.
## Available Scripts
## Admin User Management
### Create Central Admin User
```bash
npm run create-central-admin
```
Creates an administrator user in the **central database**. Central admins can:
- Manage tenants (create, update, delete)
- Access platform-wide administration features
- View all tenant information
- Manage tenant provisioning
**Interactive Mode:**
```bash
npm run create-central-admin
# You will be prompted for:
# - Email
# - Password
# - First Name (optional)
# - Last Name (optional)
# - Role (admin or superadmin)
```
**Non-Interactive Mode (using environment variables):**
```bash
EMAIL=admin@example.com PASSWORD=securepass123 FIRST_NAME=John LAST_NAME=Doe ROLE=superadmin npm run create-central-admin
```
**Logging In as Central Admin:**
1. Access the application using a central subdomain (e.g., `central.yourdomain.com` or `admin.yourdomain.com`)
2. Enter your central admin credentials
3. You'll be authenticated against the central database (not a tenant database)
**Note:** The system automatically detects if you're logging in from a central subdomain based on the `CENTRAL_SUBDOMAINS` environment variable (defaults to `central,admin`). No special UI or configuration is needed on the frontend.
### Create Tenant User
For creating users within a specific tenant database, use:
```bash
npm run create-tenant-user <tenant-slug>
# (Note: This script may need to be created or already exists)
```
## Migration Scripts
### 1. Create a New Migration

View File

@@ -43,8 +43,9 @@ function decryptPassword(encryptedPassword: string): string {
function createTenantKnexConnection(tenant: any): Knex {
const decryptedPassword = decryptPassword(tenant.dbPassword);
// Replace 'db' hostname with 'localhost' when running outside Docker
const dbHost = tenant.dbHost === 'db' ? 'localhost' : tenant.dbHost;
// Use Docker hostname 'db' when running inside container
// The dbHost will be 'db' for Docker connections or 'localhost' for local development
const dbHost = tenant.dbHost;
return knex({
client: 'mysql2',
@@ -82,7 +83,7 @@ async function migrateTenant(tenant: any): Promise<void> {
});
}
} catch (error) {
console.error(`${tenant.name}: Migration failed:`, error.message);
console.error(`${tenant.name}: Migration failed:`, error);
throw error;
} finally {
await tenantKnex.destroy();

View File

@@ -0,0 +1,181 @@
import { Knex } from 'knex';
import * as knexLib from 'knex';
/**
* Create a Knex connection for tenant database
*/
function createKnexConnection(database: string): Knex {
return knexLib.default({
client: 'mysql2',
connection: {
host: process.env.DB_HOST || 'db',
port: parseInt(process.env.DB_PORT || '3306'),
user: 'root',
password: 'asjdnfqTash37faggT',
database: database,
},
});
}
interface RoleWithPermissions {
name: string;
description: string;
objectPermissions: {
[objectApiName: string]: {
canCreate: boolean;
canRead: boolean;
canEdit: boolean;
canDelete: boolean;
canViewAll: boolean;
canModifyAll: boolean;
};
};
}
const DEFAULT_ROLES: RoleWithPermissions[] = [
{
name: 'System Administrator',
description: 'Full access to all objects and records. Can view and modify all data.',
objectPermissions: {
'*': {
canCreate: true,
canRead: true,
canEdit: true,
canDelete: true,
canViewAll: true,
canModifyAll: true,
},
},
},
{
name: 'Standard User',
description: 'Can create, read, edit, and delete own records. Respects OWD settings.',
objectPermissions: {
'*': {
canCreate: true,
canRead: true,
canEdit: true,
canDelete: true,
canViewAll: false,
canModifyAll: false,
},
},
},
{
name: 'Read Only',
description: 'Can only read records based on OWD settings. No create, edit, or delete.',
objectPermissions: {
'*': {
canCreate: false,
canRead: true,
canEdit: false,
canDelete: false,
canViewAll: false,
canModifyAll: false,
},
},
},
];
async function seedRolesForTenant(knex: Knex, tenantName: string) {
console.log(`\n🌱 Seeding roles for tenant: ${tenantName}`);
// Get all object definitions
const objectDefinitions = await knex('object_definitions').select('id', 'apiName');
for (const roleData of DEFAULT_ROLES) {
// Check if role already exists
const existingRole = await knex('roles')
.where({ name: roleData.name })
.first();
let roleId: string;
if (existingRole) {
console.log(` Role "${roleData.name}" already exists, skipping...`);
roleId = existingRole.id;
} else {
// Create role
await knex('roles').insert({
name: roleData.name,
guardName: 'api',
description: roleData.description,
});
// Get the inserted role
const newRole = await knex('roles')
.where({ name: roleData.name })
.first();
roleId = newRole.id;
console.log(` ✅ Created role: ${roleData.name}`);
}
// Create object permissions for all objects
const wildcardPermissions = roleData.objectPermissions['*'];
for (const objectDef of objectDefinitions) {
// Check if permission already exists
const existingPermission = await knex('role_object_permissions')
.where({
roleId: roleId,
objectDefinitionId: objectDef.id,
})
.first();
if (!existingPermission) {
await knex('role_object_permissions').insert({
roleId: roleId,
objectDefinitionId: objectDef.id,
canCreate: wildcardPermissions.canCreate,
canRead: wildcardPermissions.canRead,
canEdit: wildcardPermissions.canEdit,
canDelete: wildcardPermissions.canDelete,
canViewAll: wildcardPermissions.canViewAll,
canModifyAll: wildcardPermissions.canModifyAll,
});
}
}
console.log(` 📋 Set permissions for ${objectDefinitions.length} objects`);
}
}
async function seedAllTenants() {
console.log('🚀 Starting role seeding for all tenants...\n');
// For now, seed the main tenant database
const databases = ['tenant_tenant1'];
let successCount = 0;
let errorCount = 0;
for (const database of databases) {
try {
const knex = createKnexConnection(database);
await seedRolesForTenant(knex, database);
await knex.destroy();
successCount++;
} catch (error) {
console.error(`${database}: Seeding failed:`, error.message);
errorCount++;
}
}
console.log('\n============================================================');
console.log('📊 Seeding Summary');
console.log('============================================================');
console.log(`✅ Successful: ${successCount}`);
console.log(`❌ Failed: ${errorCount}`);
if (errorCount === 0) {
console.log('\n🎉 All tenant roles seeded successfully!');
}
}
seedAllTenants()
.then(() => process.exit(0))
.catch((error) => {
console.error('Unhandled error:', error);
process.exit(1);
});