Renaming from tenant to organization
This commit is contained in:
parent
bf50563ab7
commit
087f8ce0e9
16 changed files with 129 additions and 243 deletions
|
|
@ -17,7 +17,7 @@ namespace Core.Entities.Users
|
||||||
public DateTime? LastLoginDate { get; set; }
|
public DateTime? LastLoginDate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Tenant
|
public class Organization
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
|
|
@ -26,10 +26,10 @@ namespace Core.Entities.Users
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserTenant
|
public class UserOrganization
|
||||||
{
|
{
|
||||||
public int UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
public int TenantId { get; set; }
|
public int OrganizationId { get; set; }
|
||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,8 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
|
||||||
void CreateConfigurationIndexes()
|
void CreateConfigurationIndexes()
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
CREATE INDEX idx_app_configuration_key ON app_configuration(""key"");
|
CREATE INDEX IF NOT EXISTS 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_validity ON app_configuration(valid_from, expires_at);";
|
||||||
ExecuteSql(sql);
|
ExecuteSql(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE TRIGGER trg_app_configuration_modified_at
|
CREATE OR REPLACE TRIGGER trg_app_configuration_modified_at
|
||||||
BEFORE UPDATE ON app_configuration
|
BEFORE UPDATE ON app_configuration
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_app_configuration_modified_at();";
|
EXECUTE FUNCTION update_app_configuration_modified_at();";
|
||||||
|
|
@ -124,7 +124,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
CREATE TRIGGER trg_app_configuration_notify
|
CREATE OR REPLACE TRIGGER trg_app_configuration_notify
|
||||||
AFTER INSERT OR UPDATE ON app_configuration
|
AFTER INSERT OR UPDATE ON app_configuration
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION notify_app_configuration_change();";
|
EXECUTE FUNCTION notify_app_configuration_change();";
|
||||||
|
|
@ -169,7 +169,7 @@ public class SetupConfiguration : Core.IDbConfigure<SetupConfiguration.Command>
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ 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
|
AFTER INSERT OR UPDATE OR DELETE ON app_configuration
|
||||||
FOR EACH ROW EXECUTE FUNCTION log_app_configuration_changes();";
|
FOR EACH ROW EXECUTE FUNCTION log_app_configuration_changes();";
|
||||||
ExecuteSql(sql);
|
ExecuteSql(sql);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ namespace Database.Core.DDL
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CreateUsersTable();
|
CreateUsersTable();
|
||||||
CreateTenantsTable();
|
CreateOrganizationsTable();
|
||||||
CreateUserTenantsTable();
|
CreateUserOrganizationsTable();
|
||||||
SetupRLS();
|
SetupRLS();
|
||||||
|
|
||||||
_transaction.Commit();
|
_transaction.Commit();
|
||||||
|
|
@ -78,12 +78,12 @@ namespace Database.Core.DDL
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the tenants table
|
/// Creates the organizations table
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CreateTenantsTable()
|
void CreateOrganizationsTable()
|
||||||
{
|
{
|
||||||
var sql = @"
|
var sql = @"
|
||||||
CREATE TABLE IF NOT EXISTS tenants (
|
CREATE TABLE IF NOT EXISTS organizations (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
connection_string VARCHAR(500) NOT NULL,
|
connection_string VARCHAR(500) NOT NULL,
|
||||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
|
@ -96,17 +96,17 @@ namespace Database.Core.DDL
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the user_tenants table
|
/// Creates the user_organizations table
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CreateUserTenantsTable()
|
void CreateUserOrganizationsTable()
|
||||||
{
|
{
|
||||||
var sql = @"
|
var sql = @"
|
||||||
CREATE TABLE IF NOT EXISTS user_tenants (
|
CREATE TABLE IF NOT EXISTS user_organizations (
|
||||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
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,
|
pin_code VARCHAR(10) NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (user_id, tenant_id)
|
PRIMARY KEY (user_id, organization_id)
|
||||||
);";
|
);";
|
||||||
|
|
||||||
ExecuteSql(sql);
|
ExecuteSql(sql);
|
||||||
|
|
@ -114,23 +114,23 @@ namespace Database.Core.DDL
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
void SetupRLS()
|
void SetupRLS()
|
||||||
{
|
{
|
||||||
var sql = new[]
|
var sql = new[]
|
||||||
{
|
{
|
||||||
"ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;",
|
"ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;",
|
||||||
"ALTER TABLE user_tenants ENABLE ROW LEVEL SECURITY;",
|
"ALTER TABLE user_organizations ENABLE ROW LEVEL SECURITY;",
|
||||||
"DROP POLICY IF EXISTS tenant_access ON tenants;",
|
"DROP POLICY IF EXISTS organization_access ON organizations;",
|
||||||
@"CREATE POLICY tenant_access ON tenants
|
@"CREATE POLICY organization_access ON organizations
|
||||||
USING (id IN (
|
USING (id IN (
|
||||||
SELECT tenant_id
|
SELECT organization_id
|
||||||
FROM user_tenants
|
FROM user_organizations
|
||||||
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER
|
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER
|
||||||
));",
|
));",
|
||||||
"DROP POLICY IF EXISTS user_tenant_access ON user_tenants;",
|
"DROP POLICY IF EXISTS user_organization_access ON user_organizations;",
|
||||||
@"CREATE POLICY user_tenant_access ON user_tenants
|
@"CREATE POLICY user_organization_access ON user_organizations
|
||||||
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);"
|
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ namespace Database.Core
|
||||||
{
|
{
|
||||||
public class UserService
|
public class UserService
|
||||||
{
|
{
|
||||||
|
public record UserCreateCommand(string CorrelationId, string Email, string Password);
|
||||||
|
|
||||||
private readonly IDbConnection _db;
|
private readonly IDbConnection _db;
|
||||||
|
|
||||||
public UserService(IDbConnection db)
|
public UserService(IDbConnection db)
|
||||||
|
|
@ -13,56 +15,57 @@ namespace Database.Core
|
||||||
_db = db;
|
_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)
|
var user = new User
|
||||||
_db.Open();
|
|
||||||
|
|
||||||
using var transaction = _db.BeginTransaction();
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Create user
|
Email = command.Email,
|
||||||
var user = new User
|
PasswordHash = PasswordHasher.HashPassword(command.Password),
|
||||||
{
|
SecurityStamp = Guid.NewGuid().ToString(),
|
||||||
Email = email,
|
EmailConfirmed = false,
|
||||||
PasswordHash = PasswordHasher.HashPassword(password),
|
CreatedDate = DateTime.UtcNow
|
||||||
SecurityStamp = Guid.NewGuid().ToString(),
|
};
|
||||||
EmailConfirmed = false,
|
|
||||||
CreatedDate = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
var userId = await _db.ExecuteScalarAsync<int>(@$"
|
var userId = await _db.ExecuteScalarAsync<int>(@$"
|
||||||
INSERT INTO {schema}.users (email, password_hash, security_stamp, email_confirmed, created_date)
|
INSERT INTO users (email, password_hash, security_stamp, email_confirmed, created_at)
|
||||||
VALUES (@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @CreatedDate)
|
VALUES (@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed, @CreatedDate)
|
||||||
RETURNING id", user);
|
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,
|
CreatedDate = DateTime.UtcNow,
|
||||||
CreatedBy = userId,
|
CreatedBy = userId,
|
||||||
IsActive = true
|
IsActive = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var tenantId = await _db.ExecuteScalarAsync<int>(@$"
|
var organizationId = await _db.ExecuteScalarAsync<int>(@$"
|
||||||
INSERT INTO {schema}.tenants (connection_string, created_date, created_by, is_active)
|
INSERT INTO {schema}.organizations (connection_string, created_date, created_by, is_active)
|
||||||
VALUES (@ConnectionString, @CreatedDate, @CreatedBy, @IsActive)
|
VALUES (@ConnectionString, @CreatedDate, @CreatedBy, @IsActive)
|
||||||
RETURNING id", tenant);
|
RETURNING id", organization);
|
||||||
|
|
||||||
// Link user to tenant
|
// Link user to organization
|
||||||
var userTenant = new UserTenant
|
var userOrganization = new UserOrganization
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
TenantId = tenantId,
|
OrganizationId = organizationId,
|
||||||
CreatedDate = DateTime.UtcNow
|
CreatedDate = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
await _db.ExecuteAsync(@$"
|
await _db.ExecuteAsync(@$"
|
||||||
INSERT INTO {schema}.user_tenants (user_id, tenant_id, created_date)
|
INSERT INTO {schema}.user_organizations (user_id, organization_id, created_date)
|
||||||
VALUES (@UserId, @TenantId, @CreatedDate)", userTenant);
|
VALUES (@UserId, @OrganizationId, @CreatedDate)", userOrganization);
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
namespace Database.Tenants
|
namespace Database.Organizations
|
||||||
{
|
{
|
||||||
internal class InitializeTenantData
|
internal class InitializeOrganizationData
|
||||||
{
|
{
|
||||||
private readonly IDbConnection _db;
|
private readonly IDbConnection _db;
|
||||||
|
|
||||||
public InitializeTenantData(IDbConnection db)
|
public InitializeOrganizationData(IDbConnection db)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ namespace SetupInfrastructure
|
||||||
sw.Start();
|
sw.Start();
|
||||||
var setupApplicationUser = _container.Resolve<Database.Core.DCL.SetupApplicationUser>();
|
var setupApplicationUser = _container.Resolve<Database.Core.DCL.SetupApplicationUser>();
|
||||||
setupApplicationUser.With(new Database.Core.DCL.SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" });
|
setupApplicationUser.With(new Database.Core.DCL.SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" });
|
||||||
Console.Write($"DONE, took: {sw.ElapsedMilliseconds} ms");
|
Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms");
|
||||||
|
|
||||||
Console.WriteLine("::");
|
Console.WriteLine("::");
|
||||||
Console.WriteLine("::");
|
Console.WriteLine("::");
|
||||||
|
|
@ -64,12 +64,12 @@ namespace SetupInfrastructure
|
||||||
|
|
||||||
var setupIdentitySystem = _container.Resolve<Database.Core.DDL.SetupIdentitySystem>();
|
var setupIdentitySystem = _container.Resolve<Database.Core.DDL.SetupIdentitySystem>();
|
||||||
setupIdentitySystem.With(new Database.Core.DDL.SetupIdentitySystem.Command());
|
setupIdentitySystem.With(new Database.Core.DDL.SetupIdentitySystem.Command());
|
||||||
Console.Write($"DONE, took: {sw.ElapsedMilliseconds} ms");
|
Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms");
|
||||||
|
|
||||||
Console.WriteLine("::");
|
Console.WriteLine("::");
|
||||||
Console.WriteLine("::");
|
Console.WriteLine("::");
|
||||||
|
|
||||||
Console.Write("Database.Core.DDL.SetupIdentitySystem...");
|
Console.Write("Database.ConfigurationManagementSystem.SetupConfiguration...");
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
var setupConfigurationSystem = _container.Resolve<Database.ConfigurationManagementSystem.SetupConfiguration>();
|
var setupConfigurationSystem = _container.Resolve<Database.ConfigurationManagementSystem.SetupConfiguration>();
|
||||||
setupConfigurationSystem.With(new Database.ConfigurationManagementSystem.SetupConfiguration.Command());
|
setupConfigurationSystem.With(new Database.ConfigurationManagementSystem.SetupConfiguration.Command());
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -9,7 +9,8 @@
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"host": "192.168.1.57",
|
"host": "192.168.1.57",
|
||||||
"port": "5432",
|
"port": "5432",
|
||||||
"url": "jdbc:postgresql://192.168.1.57:5432/",
|
"database": "ptmain",
|
||||||
|
"url": "jdbc:postgresql://192.168.1.57:5432/ptmain",
|
||||||
"configurationType": "MANUAL",
|
"configurationType": "MANUAL",
|
||||||
"home": "postgresql_client",
|
"home": "postgresql_client",
|
||||||
"type": "dev",
|
"type": "dev",
|
||||||
|
|
@ -32,34 +33,9 @@
|
||||||
"postgresql.dd.tag.string": "false"
|
"postgresql.dd.tag.string": "false"
|
||||||
},
|
},
|
||||||
"auth-model": "native"
|
"auth-model": "native"
|
||||||
}
|
},
|
||||||
},
|
"custom-properties": {
|
||||||
"postgres-jdbc-19484872d85-cd2a4a40116e706": {
|
"SQLEditor.outputPanel.autoShow": "false"
|
||||||
"provider": "postgresql",
|
|
||||||
"driver": "postgres-jdbc",
|
|
||||||
"name": "UB-KK01-sathumper",
|
|
||||||
"configuration": {
|
|
||||||
"host": "192.168.1.57",
|
|
||||||
"port": "5432",
|
|
||||||
"database": "postgres",
|
|
||||||
"url": "jdbc:postgresql://192.168.1.57:5432/postgres",
|
|
||||||
"configurationType": "MANUAL",
|
|
||||||
"home": "postgresql_client",
|
|
||||||
"type": "dev",
|
|
||||||
"closeIdleConnection": true,
|
|
||||||
"provider-properties": {
|
|
||||||
"@dbeaver-show-non-default-db@": "true",
|
|
||||||
"@dbeaver-chosen-role@": "",
|
|
||||||
"@dbeaver-show-template-db@": "false",
|
|
||||||
"@dbeaver-show-unavailable-db@": "false",
|
|
||||||
"show-database-statistics": "false",
|
|
||||||
"@dbeaver-read-all-data-types-db@": "false",
|
|
||||||
"read-keys-with-columns": "false",
|
|
||||||
"@dbeaver-use-prepared-statements-db@": "false",
|
|
||||||
"postgresql.dd.plain.string": "false",
|
|
||||||
"postgresql.dd.tag.string": "false"
|
|
||||||
},
|
|
||||||
"auth-model": "native"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"resources":{"Scripts/Script-11.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"}}}
|
{"resources":{"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}}
|
||||||
Binary file not shown.
|
|
@ -6,6 +6,43 @@
|
||||||
"driver": "postgres-jdbc",
|
"driver": "postgres-jdbc",
|
||||||
"name": "UB-KK01-postgres",
|
"name": "UB-KK01-postgres",
|
||||||
"save-password": true,
|
"save-password": true,
|
||||||
|
"configuration": {
|
||||||
|
"host": "192.168.1.57",
|
||||||
|
"port": "5432",
|
||||||
|
"database": "ptmain",
|
||||||
|
"url": "jdbc:postgresql://192.168.1.57:5432/ptmain",
|
||||||
|
"configurationType": "MANUAL",
|
||||||
|
"home": "postgresql_client",
|
||||||
|
"type": "dev",
|
||||||
|
"closeIdleConnection": true,
|
||||||
|
"properties": {
|
||||||
|
"connectTimeout": "20",
|
||||||
|
"loginTimeout": "20",
|
||||||
|
"escapeSyntaxCallMode": "callIfNoReturn"
|
||||||
|
},
|
||||||
|
"provider-properties": {
|
||||||
|
"@dbeaver-show-non-default-db@": "true",
|
||||||
|
"@dbeaver-chosen-role@": "",
|
||||||
|
"@dbeaver-show-template-db@": "false",
|
||||||
|
"@dbeaver-show-unavailable-db@": "false",
|
||||||
|
"show-database-statistics": "false",
|
||||||
|
"@dbeaver-read-all-data-types-db@": "false",
|
||||||
|
"read-keys-with-columns": "false",
|
||||||
|
"@dbeaver-use-prepared-statements-db@": "false",
|
||||||
|
"postgresql.dd.plain.string": "false",
|
||||||
|
"postgresql.dd.tag.string": "false"
|
||||||
|
},
|
||||||
|
"auth-model": "native"
|
||||||
|
},
|
||||||
|
"custom-properties": {
|
||||||
|
"SQLEditor.outputPanel.autoShow": "false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postgres-jdbc-1950174dc2f-6bd6c7100db1cc5c": {
|
||||||
|
"provider": "postgresql",
|
||||||
|
"driver": "postgres-jdbc",
|
||||||
|
"name": "UB-KK01-sathumper",
|
||||||
|
"save-password": true,
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"host": "192.168.1.57",
|
"host": "192.168.1.57",
|
||||||
"port": "5432",
|
"port": "5432",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"resources":{"Scripts/Script-11.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"sandbox"}}}
|
{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain","default-schema":"system"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01","default-schema":"public"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"}}}
|
||||||
0
SqlManagement/Scripts/Script.sql
Normal file
0
SqlManagement/Scripts/Script.sql
Normal file
|
|
@ -1,144 +1,4 @@
|
||||||
|
SELECT substring(datname, 5)::integer as dbnumber
|
||||||
drop schema "public"
|
FROM pg_database
|
||||||
show search_path;
|
WHERE datname LIKE 'ptdb%'
|
||||||
|
ORDER BY dbnumber DESC
|
||||||
set schema "ptmain";
|
|
||||||
|
|
||||||
CREATE SCHEMA if not exists ptmain;
|
|
||||||
GRANT USAGE, CREATE ON SCHEMA ptmain TO sathumper;
|
|
||||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA ptmain TO sathumper;
|
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ptmain TO sathumper;
|
|
||||||
|
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA ptmain
|
|
||||||
GRANT ALL PRIVILEGES ON TABLES TO sathumper;
|
|
||||||
|
|
||||||
|
|
||||||
-- Create schema if not exists
|
|
||||||
|
|
||||||
insert into
|
|
||||||
-- Create main configuration table
|
|
||||||
CREATE TABLE app_configuration (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
key VARCHAR(255) NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
label VARCHAR(255),
|
|
||||||
content_type VARCHAR(255) DEFAULT 'text/plain',
|
|
||||||
valid_from TIMESTAMPTZ,
|
|
||||||
expires_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
modified_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
etag UUID DEFAULT gen_random_uuid(),
|
|
||||||
CONSTRAINT app_configuration_key_unique UNIQUE (key, valid_from)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create history table
|
|
||||||
CREATE TABLE app_configuration_history (
|
|
||||||
history_id BIGSERIAL PRIMARY KEY,
|
|
||||||
operation CHAR(1) NOT NULL, -- I = Insert, U = Update, D = Delete
|
|
||||||
changed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
changed_by TEXT NOT NULL DEFAULT CURRENT_USER,
|
|
||||||
-- Original columns
|
|
||||||
id BIGINT NOT NULL,
|
|
||||||
key VARCHAR(255) NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
label VARCHAR(255),
|
|
||||||
content_type VARCHAR(255),
|
|
||||||
valid_from TIMESTAMPTZ,
|
|
||||||
expires_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL,
|
|
||||||
modified_at TIMESTAMPTZ NOT NULL,
|
|
||||||
etag UUID
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Create function for updating modified_at
|
|
||||||
CREATE OR REPLACE FUNCTION update_modified_at()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
NEW.modified_at = CURRENT_TIMESTAMP;
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
-- Create history tracking function
|
|
||||||
CREATE OR REPLACE FUNCTION track_configuration_history()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
-- Indsæt historikpost baseret på operationstypen
|
|
||||||
IF TG_OP = 'INSERT' THEN
|
|
||||||
INSERT INTO app_configuration_history(
|
|
||||||
operation,
|
|
||||||
id, key, value, label, content_type, valid_from, expires_at,
|
|
||||||
created_at, modified_at, etag
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
'I',
|
|
||||||
NEW.id, NEW.key, NEW.value, NEW.label, NEW.content_type, NEW.valid_from,
|
|
||||||
NEW.expires_at, NEW.created_at, NEW.modified_at, NEW.etag
|
|
||||||
);
|
|
||||||
ELSIF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN
|
|
||||||
-- Brug OLD for både UPDATE og DELETE
|
|
||||||
INSERT INTO app_configuration_history(
|
|
||||||
operation,
|
|
||||||
id, key, value, label, content_type, valid_from, expires_at,
|
|
||||||
created_at, modified_at, etag
|
|
||||||
)
|
|
||||||
VALUES (
|
|
||||||
CASE
|
|
||||||
WHEN TG_OP = 'UPDATE' THEN 'U'
|
|
||||||
WHEN TG_OP = 'DELETE' THEN 'D'
|
|
||||||
END,
|
|
||||||
OLD.id, OLD.key, OLD.value, OLD.label, OLD.content_type, OLD.valid_from,
|
|
||||||
OLD.expires_at, OLD.created_at, OLD.modified_at, OLD.etag
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Returner NULL for AFTER-trigger
|
|
||||||
RETURN NULL;
|
|
||||||
EXCEPTION
|
|
||||||
WHEN others THEN
|
|
||||||
-- Log fejl og rejs undtagelse
|
|
||||||
RAISE EXCEPTION 'Fejl i track_configuration_history: %', SQLERRM;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
-- Create triggers
|
|
||||||
CREATE TRIGGER trg_app_configuration_modified_at
|
|
||||||
BEFORE UPDATE ON app_configuration
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION update_modified_at();
|
|
||||||
|
|
||||||
CREATE TRIGGER trg_app_configuration_history
|
|
||||||
AFTER INSERT OR UPDATE OR DELETE ON app_configuration
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION track_configuration_history();
|
|
||||||
|
|
||||||
CREATE TRIGGER trg_app_configuration_notify
|
|
||||||
AFTER INSERT OR UPDATE ON app_configuration
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION notify_config_change();
|
|
||||||
|
|
||||||
-- Comments
|
|
||||||
COMMENT ON TABLE app_configuration IS 'Application configuration with temporal validity';
|
|
||||||
COMMENT ON TABLE app_configuration_history IS 'Historical changes to application configuration';
|
|
||||||
COMMENT ON COLUMN app_configuration_history.operation IS 'Type of operation: I=Insert, U=Update, D=Delete';
|
|
||||||
COMMENT ON COLUMN app_configuration_history.changed_at IS 'When the change occurred';
|
|
||||||
COMMENT ON COLUMN app_configuration_history.changed_by IS 'User who made the change';
|
|
||||||
|
|
||||||
|
|
||||||
DELETE from ptmain.app_configuration
|
|
||||||
|
|
||||||
-- Email templates configuration
|
|
||||||
INSERT INTO ptmain.app_configuration ("key",value,"label",content_type,valid_from,expires_at,created_at,modified_at,etag) VALUES
|
|
||||||
('Debug','true',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','f1348731-9396-4f1d-b40a-7fbd23a897d2'::uuid),
|
|
||||||
('Database:ConnectionString','"Server=db.example.com;Port=5432"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','2aa0bc3e-fa24-449a-8f25-a76d9b4d535e'::uuid),
|
|
||||||
('Database:Timeout','30',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','d25ebb14-49f6-4e33-9ac7-a3253705d0fb'::uuid),
|
|
||||||
('Database:UseSSL','true',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','f4d52ec4-b723-4561-9b18-0e7a68b89a17'::uuid),
|
|
||||||
('Logging:FileOptions','{"Path": "/var/logs/app.log", "MaxSizeMB": 100, "RetentionDays": 7}',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','06c0891d-a860-4acc-917a-d0877f511c1b'::uuid),
|
|
||||||
('Features:Experimental','{"Enabled": true, "RolloutPercentage": 25, "AllowedUserGroups": ["beta"]}',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','0136fdef-51d9-4909-82ef-f72053ce6d6d'::uuid),
|
|
||||||
('API:Endpoints','"/api/users"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','fe362b69-a486-48ad-9165-2e623e2e6f70'::uuid),
|
|
||||||
('API:Endpoints','"/api/products"',NULL,'text/plain',NULL,NULL,'2025-02-02 14:25:22.200058+01','2025-02-02 14:25:22.200058+01','c087e2d4-1f38-4814-b4dd-f30c463dc6d1'::uuid);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,14 +4,24 @@ CREATE ROLE sathumper WITH
|
||||||
LOGIN
|
LOGIN
|
||||||
PASSWORD '3911';
|
PASSWORD '3911';
|
||||||
|
|
||||||
CREATE SCHEMA ptmain;
|
CREATE SCHEMA "system";
|
||||||
GRANT USAGE, CREATE ON SCHEMA ptmain TO sathumper;
|
GRANT USAGE, CREATE ON SCHEMA "system" TO sathumper;
|
||||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA ptmain TO sathumper;
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "system" TO sathumper;
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ptmain TO sathumper;
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "system" TO sathumper;
|
||||||
|
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA ptmain
|
ALTER DEFAULT PRIVILEGES IN SCHEMA "system"
|
||||||
GRANT ALL PRIVILEGES ON TABLES TO sathumper;
|
GRANT ALL PRIVILEGES ON TABLES TO sathumper;
|
||||||
|
|
||||||
|
SELECT usename, useconfig
|
||||||
|
FROM pg_user
|
||||||
|
WHERE useconfig IS NOT NULL
|
||||||
|
AND useconfig::text LIKE '%search_path%'
|
||||||
|
|
||||||
|
|
||||||
|
ALTER ROLE sathumper1 SET search_path='ss32'
|
||||||
|
|
||||||
|
CREATE USER fm WITH PASSWORD 'asd'
|
||||||
|
|
||||||
|
await _db.ExecuteAsync(sql);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TryTenantSetupService()
|
public void TryOrganizationSetupService()
|
||||||
{
|
{
|
||||||
var conn = Container.Resolve<IDbConnection>();
|
var conn = Container.Resolve<IDbConnection>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue