Renaming from tenant to organization

This commit is contained in:
Janus C. H. Knudsen 2025-02-14 20:14:01 +01:00
parent bf50563ab7
commit 087f8ce0e9
16 changed files with 129 additions and 243 deletions

View file

@ -90,8 +90,8 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
void CreateConfigurationIndexes()
{
const string sql = @"
CREATE INDEX idx_app_configuration_key ON app_configuration(""key"");
CREATE INDEX idx_app_configuration_validity ON app_configuration(valid_from, expires_at);";
CREATE INDEX IF NOT EXISTS idx_app_configuration_key ON app_configuration(""key"");
CREATE INDEX IF NOT EXISTS idx_app_configuration_validity ON app_configuration(valid_from, expires_at);";
ExecuteSql(sql);
}
@ -106,7 +106,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_modified_at
CREATE OR REPLACE TRIGGER trg_app_configuration_modified_at
BEFORE UPDATE ON app_configuration
FOR EACH ROW
EXECUTE FUNCTION update_app_configuration_modified_at();";
@ -124,7 +124,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_notify
CREATE OR REPLACE TRIGGER trg_app_configuration_notify
AFTER INSERT OR UPDATE ON app_configuration
FOR EACH ROW
EXECUTE FUNCTION notify_app_configuration_change();";
@ -169,7 +169,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_history
CREATE OR REPLACE TRIGGER trg_app_configuration_history
AFTER INSERT OR UPDATE OR DELETE ON app_configuration
FOR EACH ROW EXECUTE FUNCTION log_app_configuration_changes();";
ExecuteSql(sql);

View file

@ -31,8 +31,8 @@ namespace Database.Core.DDL
try
{
CreateUsersTable();
CreateTenantsTable();
CreateUserTenantsTable();
CreateOrganizationsTable();
CreateUserOrganizationsTable();
SetupRLS();
_transaction.Commit();
@ -78,12 +78,12 @@ namespace Database.Core.DDL
}
/// <summary>
/// Creates the tenants table
/// Creates the organizations table
/// </summary>
void CreateTenantsTable()
void CreateOrganizationsTable()
{
var sql = @"
CREATE TABLE IF NOT EXISTS tenants (
CREATE TABLE IF NOT EXISTS organizations (
id SERIAL PRIMARY KEY,
connection_string VARCHAR(500) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
@ -96,17 +96,17 @@ namespace Database.Core.DDL
}
/// <summary>
/// Creates the user_tenants table
/// Creates the user_organizations table
/// </summary>
void CreateUserTenantsTable()
void CreateUserOrganizationsTable()
{
var sql = @"
CREATE TABLE IF NOT EXISTS user_tenants (
CREATE TABLE IF NOT EXISTS user_organizations (
user_id INTEGER NOT NULL REFERENCES users(id),
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
organization_id INTEGER NOT NULL REFERENCES organizations(id),
pin_code VARCHAR(10) NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, tenant_id)
PRIMARY KEY (user_id, organization_id)
);";
ExecuteSql(sql);
@ -114,23 +114,23 @@ namespace Database.Core.DDL
}
/// <summary>
/// Sets up Row Level Security (RLS) for the tenants and user_tenants tables.
/// Sets up Row Level Security (RLS) for the organizations and user_organizations tables.
/// </summary>
void SetupRLS()
{
var sql = new[]
{
"ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;",
"ALTER TABLE user_tenants ENABLE ROW LEVEL SECURITY;",
"DROP POLICY IF EXISTS tenant_access ON tenants;",
@"CREATE POLICY tenant_access ON tenants
"ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;",
"ALTER TABLE user_organizations ENABLE ROW LEVEL SECURITY;",
"DROP POLICY IF EXISTS organization_access ON organizations;",
@"CREATE POLICY organization_access ON organizations
USING (id IN (
SELECT tenant_id
FROM user_tenants
SELECT organization_id
FROM user_organizations
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER
));",
"DROP POLICY IF EXISTS user_tenant_access ON user_tenants;",
@"CREATE POLICY user_tenant_access ON user_tenants
"DROP POLICY IF EXISTS user_organization_access ON user_organizations;",
@"CREATE POLICY user_organization_access ON user_organizations
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);"
};

View file

@ -6,6 +6,8 @@ namespace Database.Core
{
public class UserService
{
public record UserCreateCommand(string CorrelationId, string Email, string Password);
private readonly IDbConnection _db;
public UserService(IDbConnection db)
@ -13,56 +15,57 @@ namespace Database.Core
_db = db;
}
public async Task CreateUserWithTenant(string email, string password, string tenantConnectionString)
public async Task CreateUser(UserCreateCommand command)
{
var schema = "dev";
if (_db.State != ConnectionState.Open)
_db.Open();
using var transaction = _db.BeginTransaction();
try
var user = new User
{
// Create user
var user = new User
{
Email = email,
PasswordHash = PasswordHasher.HashPassword(password),
SecurityStamp = Guid.NewGuid().ToString(),
EmailConfirmed = false,
CreatedDate = DateTime.UtcNow
};
Email = command.Email,
PasswordHash = PasswordHasher.HashPassword(command.Password),
SecurityStamp = Guid.NewGuid().ToString(),
EmailConfirmed = false,
CreatedDate = DateTime.UtcNow
};
var userId = await _db.ExecuteScalarAsync<int>(@$"
INSERT INTO {schema}.users (email, password_hash, security_stamp, email_confirmed, created_date)
var userId = await _db.ExecuteScalarAsync<int>(@$"
INSERT INTO users (email, password_hash, security_stamp, email_confirmed, created_at)
VALUES (@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @CreatedDate)
RETURNING id", user);
// Create tenant
var tenant = new Tenant
}
public async Task CreateOrganization(int userId, string organizationConnectionString)
{
var schema = "dev";
using var transaction = _db.OpenWithTransaction();
try
{
// Create organization
var organization = new Organization
{
ConnectionString = tenantConnectionString,
ConnectionString = organizationConnectionString,
CreatedDate = DateTime.UtcNow,
CreatedBy = userId,
IsActive = true
};
var tenantId = await _db.ExecuteScalarAsync<int>(@$"
INSERT INTO {schema}.tenants (connection_string, created_date, created_by, is_active)
var organizationId = await _db.ExecuteScalarAsync<int>(@$"
INSERT INTO {schema}.organizations (connection_string, created_date, created_by, is_active)
VALUES (@ConnectionString, @CreatedDate, @CreatedBy, @IsActive)
RETURNING id", tenant);
RETURNING id", organization);
// Link user to tenant
var userTenant = new UserTenant
// Link user to organization
var userOrganization = new UserOrganization
{
UserId = userId,
TenantId = tenantId,
OrganizationId = organizationId,
CreatedDate = DateTime.UtcNow
};
await _db.ExecuteAsync(@$"
INSERT INTO {schema}.user_tenants (user_id, tenant_id, created_date)
VALUES (@UserId, @TenantId, @CreatedDate)", userTenant);
INSERT INTO {schema}.user_organizations (user_id, organization_id, created_date)
VALUES (@UserId, @OrganizationId, @CreatedDate)", userOrganization);
transaction.Commit();
}

View file

@ -1,13 +1,13 @@
using Insight.Database;
using System.Data;
namespace Database.Tenants
namespace Database.Organizations
{
internal class InitializeTenantData
internal class InitializeOrganizationData
{
private readonly IDbConnection _db;
public InitializeTenantData(IDbConnection db)
public InitializeOrganizationData(IDbConnection db)
{
_db = db;
}