using System; using System.Collections.Generic; using System.Linq; using Npgsql; namespace UserPermissionTest_CS_WinForms { public class PostgreSqlUserRepository : IUserRepository { private readonly string _connectionString; public PostgreSqlUserRepository(string connectionString) { _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); InitializeDatabaseSchema(); } private void InitializeDatabaseSchema() { using (var conn = new NpgsqlConnection(_connectionString)) { conn.Open(); // Dynamic Migration Drop: If available_permissions exists with old text primary key structure bool needsRebuild = false; try { // Check if the table has the 'id' column. If not, this throws an exception, triggering the rebuild! using (var cmd = new NpgsqlCommand("SELECT id FROM available_permissions LIMIT 1;", conn)) { cmd.ExecuteNonQuery(); } } catch { needsRebuild = true; } if (needsRebuild) { using (var cmd = new NpgsqlCommand(@" DROP TABLE IF EXISTS user_permissions; DROP TABLE IF EXISTS available_permissions;", conn)) { cmd.ExecuteNonQuery(); } } // Create users table using (var cmd = new NpgsqlCommand(@" CREATE TABLE IF NOT EXISTS users ( username VARCHAR(50) PRIMARY KEY, fullname VARCHAR(100) NOT NULL, password VARCHAR(255) NOT NULL );", conn)) { cmd.ExecuteNonQuery(); } // Create available_permissions table (ID-based) using (var cmd = new NpgsqlCommand(@" CREATE TABLE IF NOT EXISTS available_permissions ( id INT PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL );", conn)) { cmd.ExecuteNonQuery(); } // Create user_permissions table (mapping username to permission_id) using (var cmd = new NpgsqlCommand(@" CREATE TABLE IF NOT EXISTS user_permissions ( username VARCHAR(50) REFERENCES users(username) ON DELETE CASCADE, permission_id INT REFERENCES available_permissions(id) ON DELETE CASCADE, PRIMARY KEY (username, permission_id) );", conn)) { cmd.ExecuteNonQuery(); } // 1. Auto-Seed default available permissions if empty using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM available_permissions;", conn)) { long count = (long)cmd.ExecuteScalar(); if (count == 0) { foreach (var perm in GetDefaultPermissions()) { using (var insertCmd = new NpgsqlCommand(@" INSERT INTO available_permissions (id, name) VALUES (@id, @name) ON CONFLICT (id) DO NOTHING;", conn)) { insertCmd.Parameters.AddWithValue("@id", perm.Id); insertCmd.Parameters.AddWithValue("@name", perm.Name); insertCmd.ExecuteNonQuery(); } } } } // 2. Auto-Seed default users if empty using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM users;", conn)) { long count = (long)cmd.ExecuteScalar(); if (count == 0) { foreach (var user in GetDefaultUsers()) { using (var insertCmd = new NpgsqlCommand(@" INSERT INTO users (username, fullname, password) VALUES (@username, @fullname, @password) ON CONFLICT (username) DO NOTHING;", conn)) { insertCmd.Parameters.AddWithValue("@username", user.Username); insertCmd.Parameters.AddWithValue("@fullname", user.FullName); insertCmd.Parameters.AddWithValue("@password", user.Password); insertCmd.ExecuteNonQuery(); } } } } // 3. Self-healing permissions: check if 'admin' exists but has 0 permissions, and restore using (var checkCmd = new NpgsqlCommand("SELECT COUNT(*) FROM users WHERE username = 'admin';", conn)) { long adminExists = (long)checkCmd.ExecuteScalar(); if (adminExists > 0) { using (var permCountCmd = new NpgsqlCommand("SELECT COUNT(*) FROM user_permissions WHERE username = 'admin';", conn)) { long adminPermsCount = (long)permCountCmd.ExecuteScalar(); if (adminPermsCount == 0) { foreach (int permId in new int[] { 1, 2, 3, 4, 5 }) { using (var insertCmd = new NpgsqlCommand(@" INSERT INTO user_permissions (username, permission_id) VALUES ('admin', @permId) ON CONFLICT DO NOTHING;", conn)) { insertCmd.Parameters.AddWithValue("@permId", permId); insertCmd.ExecuteNonQuery(); } } } } } } // 4. Self-healing permissions: check if 'user' exists but has 0 permissions, and restore using (var checkCmd = new NpgsqlCommand("SELECT COUNT(*) FROM users WHERE username = 'user';", conn)) { long userExists = (long)checkCmd.ExecuteScalar(); if (userExists > 0) { using (var permCountCmd = new NpgsqlCommand("SELECT COUNT(*) FROM user_permissions WHERE username = 'user';", conn)) { long userPermsCount = (long)permCountCmd.ExecuteScalar(); if (userPermsCount == 0) { using (var insertCmd = new NpgsqlCommand(@" INSERT INTO user_permissions (username, permission_id) VALUES ('user', 1) ON CONFLICT DO NOTHING;", conn)) { insertCmd.ExecuteNonQuery(); } } } } } } } public List LoadUsers() { var users = new List(); try { using (var conn = new NpgsqlConnection(_connectionString)) { conn.Open(); // Read users using (var cmd = new NpgsqlCommand("SELECT username, fullname, password FROM users;", conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { users.Add(new User { Username = reader.GetString(0), FullName = reader.GetString(1), Password = reader.GetString(2), Permissions = new List() }); } } // Read permission IDs for each user foreach (var user in users) { using (var cmd = new NpgsqlCommand("SELECT permission_id FROM user_permissions WHERE username = @username;", conn)) { cmd.Parameters.AddWithValue("@username", user.Username); using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { user.Permissions.Add(reader.GetInt32(0)); } } } } } } catch (Exception ex) { Console.WriteLine("Error loading users from PostgreSQL: " + ex.Message); } // Fallback if no users exist in database if (users.Count == 0) { var defaults = GetDefaultUsers(); SaveUsers(defaults); return defaults; } return users; } public void SaveUsers(List users) { try { using (var conn = new NpgsqlConnection(_connectionString)) { conn.Open(); using (var trans = conn.BeginTransaction()) { // 1. Delete users from database that are no longer in the input list var currentInDb = new List(); using (var cmd = new NpgsqlCommand("SELECT username FROM users;", conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { currentInDb.Add(reader.GetString(0)); } } var usernamesToKeep = new HashSet(users.Select(u => u.Username), StringComparer.OrdinalIgnoreCase); foreach (var dbUser in currentInDb) { if (!usernamesToKeep.Contains(dbUser)) { using (var cmd = new NpgsqlCommand("DELETE FROM users WHERE username = @username;", conn)) { cmd.Parameters.AddWithValue("@username", dbUser); cmd.ExecuteNonQuery(); } } } // 2. Insert or Update users and sync permissions foreach (var user in users) { using (var cmd = new NpgsqlCommand(@" INSERT INTO users (username, fullname, password) VALUES (@username, @fullname, @password) ON CONFLICT (username) DO UPDATE SET fullname = EXCLUDED.fullname, password = EXCLUDED.password;", conn)) { cmd.Parameters.AddWithValue("@username", user.Username); cmd.Parameters.AddWithValue("@fullname", user.FullName); cmd.Parameters.AddWithValue("@password", user.Password); cmd.ExecuteNonQuery(); } // Re-sync permissions: clear old permissions and write current ones using (var cmd = new NpgsqlCommand("DELETE FROM user_permissions WHERE username = @username;", conn)) { cmd.Parameters.AddWithValue("@username", user.Username); cmd.ExecuteNonQuery(); } foreach (var permissionId in user.Permissions) { using (var cmd = new NpgsqlCommand(@" INSERT INTO user_permissions (username, permission_id) VALUES (@username, @permissionId) ON CONFLICT DO NOTHING;", conn)) { cmd.Parameters.AddWithValue("@username", user.Username); cmd.Parameters.AddWithValue("@permissionId", permissionId); cmd.ExecuteNonQuery(); } } } trans.Commit(); } } } catch (Exception ex) { Console.WriteLine("Error saving users to PostgreSQL: " + ex.Message); } } public List LoadPermissions() { var permissions = new List(); try { using (var conn = new NpgsqlConnection(_connectionString)) { conn.Open(); using (var cmd = new NpgsqlCommand("SELECT id, name FROM available_permissions ORDER BY id;", conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { permissions.Add(new Permission { Id = reader.GetInt32(0), Name = reader.GetString(1) }); } } } } catch (Exception ex) { Console.WriteLine("Error loading permissions from PostgreSQL: " + ex.Message); } if (permissions.Count == 0) { var defaults = GetDefaultPermissions(); SavePermissions(defaults); return defaults; } return permissions; } public void SavePermissions(List permissions) { try { using (var conn = new NpgsqlConnection(_connectionString)) { conn.Open(); using (var trans = conn.BeginTransaction()) { // Sync system permissions var currentInDb = new List(); using (var cmd = new NpgsqlCommand("SELECT id FROM available_permissions;", conn)) using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { currentInDb.Add(reader.GetInt32(0)); } } var permissionsToKeep = new HashSet(permissions.Select(p => p.Id)); foreach (var dbPermId in currentInDb) { if (!permissionsToKeep.Contains(dbPermId)) { using (var cmd = new NpgsqlCommand("DELETE FROM available_permissions WHERE id = @id;", conn)) { cmd.Parameters.AddWithValue("@id", dbPermId); cmd.ExecuteNonQuery(); } } } foreach (var perm in permissions) { using (var cmd = new NpgsqlCommand(@" INSERT INTO available_permissions (id, name) VALUES (@id, @name) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;", conn)) { cmd.Parameters.AddWithValue("@id", perm.Id); cmd.Parameters.AddWithValue("@name", perm.Name); cmd.ExecuteNonQuery(); } } trans.Commit(); } } } catch (Exception ex) { Console.WriteLine("Error saving permissions to PostgreSQL: " + ex.Message); } } private List GetDefaultUsers() { return new List { new User { Username = "admin", FullName = "System Administrator", Password = PasswordHasher.HashPassword("admin"), Permissions = new List { 1, 2, 3, 4, 5 } }, new User { Username = "user", FullName = "Standard User", Password = PasswordHasher.HashPassword("user"), Permissions = new List { 1 } } }; } private List GetDefaultPermissions() { return new List { new Permission { Id = 1, Name = "View Dashboard" }, new Permission { Id = 2, Name = "Edit Settings" }, new Permission { Id = 3, Name = "Manage Users" }, new Permission { Id = 4, Name = "Full Control" }, new Permission { Id = 5, Name = "Delete Transactions" } }; } } }