diff --git a/misc/create_schema.sql b/misc/create_schema.sql
index 8df7d30..41b4378 100644
--- a/misc/create_schema.sql
+++ b/misc/create_schema.sql
@@ -101,7 +101,6 @@ CREATE TABLE `participant` (
`street` VARCHAR(128) NULL DEFAULT NULL,
`postal_code` VARCHAR(5) NULL DEFAULT NULL,
`city` VARCHAR(64) NULL DEFAULT NULL,
- `roles` INT(10) UNSIGNED NULL DEFAULT 0 COMMENT 'Bitarray of assigned roles',
`flags` INT(10) UNSIGNED NULL DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
@@ -175,6 +174,8 @@ CREATE TABLE `role` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '0' COMMENT 'unique role name',
`description` VARCHAR(255) NULL DEFAULT '0' COMMENT 'role description',
+ `created` DATETIME NULL DEFAULT current_timestamp(),
+ `modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE INDEX `name` (`name`)
)
@@ -185,8 +186,10 @@ ENGINE=InnoDB
CREATE TABLE `securable` (
- `id` INT(10) UNSIGNED NOT NULL,
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
+ `created` DATETIME NULL DEFAULT current_timestamp(),
+ `modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE INDEX `name` (`name`)
)
@@ -199,6 +202,8 @@ CREATE TABLE `user_role_map` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT(10) UNSIGNED NOT NULL DEFAULT 0,
`role_id` INT(10) UNSIGNED NOT NULL DEFAULT 0,
+ `created` DATETIME NULL DEFAULT current_timestamp(),
+ `modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_USER_ROLE` (`user_id`),
INDEX `FK_ROLE_USER` (`role_id`),
@@ -211,9 +216,11 @@ ENGINE=InnoDB
;
CREATE TABLE `role_securable_map` (
- `id` INT(10) UNSIGNED NOT NULL,
+ `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` INT(10) UNSIGNED NOT NULL,
`securable_id` INT(10) UNSIGNED NOT NULL,
+ `created` DATETIME NULL DEFAULT current_timestamp(),
+ `modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_ROLE_SECURABLE` (`role_id`),
INDEX `FK_SECURABLE_ROLE` (`securable_id`),
diff --git a/src/RoleEditor/App.config b/src/RoleEditor/App.config
new file mode 100644
index 0000000..49cc43e
--- /dev/null
+++ b/src/RoleEditor/App.config
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/RoleEditor/App.xaml b/src/RoleEditor/App.xaml
new file mode 100644
index 0000000..7f5413e
--- /dev/null
+++ b/src/RoleEditor/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/src/RoleEditor/App.xaml.cs b/src/RoleEditor/App.xaml.cs
new file mode 100644
index 0000000..6d8bf95
--- /dev/null
+++ b/src/RoleEditor/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace RoleEditor
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/src/RoleEditor/AssemblyInfo.cs b/src/RoleEditor/AssemblyInfo.cs
new file mode 100644
index 0000000..8b5504e
--- /dev/null
+++ b/src/RoleEditor/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/src/RoleEditor/MainWindow.xaml b/src/RoleEditor/MainWindow.xaml
new file mode 100644
index 0000000..a8662ba
--- /dev/null
+++ b/src/RoleEditor/MainWindow.xaml
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RoleEditor/MainWindow.xaml.cs b/src/RoleEditor/MainWindow.xaml.cs
new file mode 100644
index 0000000..ea7d758
--- /dev/null
+++ b/src/RoleEditor/MainWindow.xaml.cs
@@ -0,0 +1,413 @@
+
+using System;
+using System.Collections.ObjectModel;
+using System.Security.Cryptography;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+
+using brecal.model;
+using brecal.mysql;
+
+namespace RoleEditor
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ #region private fields
+
+ private readonly ObservableCollection _participants = new ObservableCollection();
+ private readonly ObservableCollection _roles = new ObservableCollection();
+ private readonly ObservableCollection _securables = new ObservableCollection();
+ private readonly ObservableCollection _users = new ObservableCollection();
+ private readonly ObservableCollection _assignedRoles = new ObservableCollection();
+ private readonly ObservableCollection _assignedSecurables = new ObservableCollection();
+ private DBManager _dbManager;
+
+ #endregion
+
+ #region Construction / Loading
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ _dbManager = new();
+ }
+
+ private async void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ // try database connection
+ try
+ {
+ // load all participants
+ foreach(Participant p in await Participant.LoadAll(_dbManager))
+ _participants.Add(p);
+ this.listBoxParticipant.ItemsSource = _participants;
+
+ // load all roles
+ foreach(Role r in await Role.LoadAll(_dbManager))
+ _roles.Add(r);
+ this.listBoxRoles.ItemsSource = _roles;
+
+ // load all securables
+ foreach(Securable s in await Securable.LoadAll(_dbManager))
+ _securables.Add(s);
+ this.listBoxSecurables.ItemsSource = _securables;
+
+ // set other item sources (filled later after selection)
+ this.listBoxUser.ItemsSource = _users;
+ this.listBoxRoleSecurables.ItemsSource = _assignedSecurables;
+ this.listBoxUserRoles.ItemsSource = _assignedRoles;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Database connection couldn't be established: {ex.Message}");
+ this.Close();
+ }
+ }
+
+ #endregion
+
+ #region button callbacks
+
+ private async void buttonParticipantSave_Click(object sender, RoutedEventArgs e)
+ {
+ Participant? p = this.listBoxParticipant.SelectedItem as Participant;
+ if (p != null)
+ {
+ p.Name = this.textBoxParticipantName.Text.Trim();
+ p.Street = this.textBoxParticipantStreet.Text.Trim();
+ p.PostalCode = this.textBoxParticipantPostalCode.Text.Trim();
+ p.City = this.textBoxParticipantCity.Text.Trim();
+ await p.Save(_dbManager);
+ this.listBoxParticipant.ItemsSource = null;
+ this.listBoxParticipant.ItemsSource = _users;
+ this.listBoxParticipant.SelectedItem = p;
+ }
+ }
+
+ private async void buttonUserSave_Click(object sender, RoutedEventArgs e)
+ {
+ User? u = this.listBoxUser.SelectedItem as User;
+ if(u != null)
+ {
+ u.Firstname = this.textBoxUserFirstName.Text.Trim();
+ u.Lastname = this.textBoxUserLastName.Text.Trim();
+ u.Username = this.textBoxUserUserName.Text.Trim();
+ if(this.textBoxUserPassword.Text.Trim().Length > 0 )
+ {
+ var data = Encoding.UTF8.GetBytes(this.textBoxUserPassword.Text.Trim());
+ using SHA512 sha = SHA512.Create();
+ byte[] hashedInputBytes = sha.ComputeHash(data);
+ var hashedInputStringBuilder = new StringBuilder(128);
+ foreach (var b in hashedInputBytes)
+ hashedInputStringBuilder.Append(b.ToString("X2"));
+ u.PasswordHash = hashedInputStringBuilder.ToString();
+ }
+ u.APIKey = this.textBoxUserAPIKey.Text.Trim();
+ await u.Save(_dbManager);
+ this.listBoxUser.ItemsSource = null;
+ this.listBoxUser.ItemsSource = _users;
+ this.listBoxUser.SelectedItem = u;
+ }
+ }
+
+ private async void buttonAddRole_Click(object sender, RoutedEventArgs e)
+ {
+ Role? r = this.listBoxRoles.SelectedItem as Role;
+ User? u = this.listBoxUser.SelectedItem as User;
+ if((r != null) && (u != null))
+ {
+ // test if assignment is already present
+ bool foundMatchingAssignment = false;
+ foreach(RoleAssignment ra in _assignedRoles)
+ {
+ if((ra.UserId == u.Id) && (ra.RoleId == r.Id))
+ {
+ foundMatchingAssignment = true;
+ break;
+ }
+ }
+
+ if(!foundMatchingAssignment)
+ {
+ RoleAssignment ra = new RoleAssignment();
+ ra.UserId = (int) u.Id;
+ ra.RoleId = (int) r.Id;
+ ra.AssignedRole = r;
+ ra.AssignedUser = u;
+ await ra.Save(_dbManager);
+ _assignedRoles.Add(ra);
+ }
+ }
+ }
+
+ private async void buttonRemoveRole_Click(object sender, RoutedEventArgs e)
+ {
+ // remove role from user
+ RoleAssignment? ra = this.listBoxUserRoles.SelectedItem as RoleAssignment;
+ if(ra != null)
+ {
+ await ra.Delete(_dbManager);
+ if(_assignedRoles.Contains(ra))
+ _assignedRoles.Remove(ra);
+ }
+ }
+
+ private async void buttonAddSecurable_Click(object sender, RoutedEventArgs e)
+ {
+ if ((this.listBoxRoles.SelectedItem is Role r) && (this.listBoxSecurables.SelectedItem is Securable s))
+ {
+ // test if assignment is already present
+ bool foundMatchingAssignment = false;
+ foreach (SecurableAssignment sa in _assignedSecurables)
+ {
+ if ((sa.SecurableId == s.Id) && (sa.RoleId == r.Id))
+ {
+ foundMatchingAssignment = true;
+ break;
+ }
+ }
+
+ if (!foundMatchingAssignment)
+ {
+ SecurableAssignment sa = new SecurableAssignment();
+ sa.SecurableId = (int)s.Id;
+ sa.RoleId = (int)r.Id;
+ sa.AssignedRole = r;
+ sa.AssignedSecurable = s;
+ await sa.Save(_dbManager);
+ _assignedSecurables.Add(sa);
+ }
+ }
+ }
+
+ private async void buttonRemoveSecurable_Click(object sender, RoutedEventArgs e)
+ {
+ SecurableAssignment? sa = this.listBoxRoleSecurables.SelectedItem as SecurableAssignment;
+ if(sa != null)
+ {
+ await sa.Delete(_dbManager);
+ if (_assignedSecurables.Contains(sa))
+ _assignedSecurables.Remove(sa);
+ }
+ }
+
+ private async void buttonSaveSecurable_Click(object sender, RoutedEventArgs e)
+ {
+ Securable? s = this.listBoxSecurables.SelectedItem as Securable;
+ if(s != null)
+ {
+ s.Name = this.textBoxSecurableName.Text.Trim();
+ await s.Save(_dbManager);
+ this.listBoxSecurables.ItemsSource = null;
+ this.listBoxSecurables.ItemsSource = _securables;
+ this.listBoxSecurables.SelectedItem = s;
+ }
+ }
+
+ private async void buttonSaveRole_Click(object sender, RoutedEventArgs e)
+ {
+ Role? r = this.listBoxRoles.SelectedItem as Role;
+ if(r != null)
+ {
+ r.Name = this.textBoxRoleName.Text.Trim();
+ r.Description = this.textBoxRoleDescription.Text.Trim();
+ await r.Save(_dbManager);
+ this.listBoxRoles.ItemsSource = null;
+ this.listBoxRoles.ItemsSource = _roles;
+ this.listBoxRoles.SelectedItem = r;
+ }
+ }
+
+ #endregion
+
+ #region listbox selection callbacks
+
+ private async void listBoxParticipant_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ Participant? p = this.listBoxParticipant.SelectedItem as Participant;
+
+ this.textBoxParticipantName.Text = (p != null) ? p.Name : string.Empty;
+ this.textBoxParticipantStreet.Text = (p != null) ? p.Street : string.Empty;
+ this.textBoxParticipantPostalCode.Text = (p != null) ? p.PostalCode : string.Empty;
+ this.textBoxParticipantCity.Text = (p != null) ? p.City : string.Empty;
+ // this.checkboxParticipantActive.Checked = (p != null) ? p.
+ this.textBoxParticipantCreated.Text = (p != null) ? p.Created.ToString() : string.Empty;
+ this.textBoxParticipantModified.Text = (p != null) ? p.Modified.ToString() : string.Empty;
+
+ // -> load users for this participant selection
+ this._users.Clear();
+ foreach (User u in await User.LoadForParticipant(p, _dbManager))
+ _users.Add(u);
+ }
+
+ private async void listBoxRoles_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ Role? r = this.listBoxRoles.SelectedItem as Role;
+ this.textBoxRoleName.Text = (r != null) ? r.Name : string.Empty;
+ this.textBoxRoleDescription.Text = (r != null) ? r.Description : string.Empty;
+
+ _assignedSecurables.Clear();
+ if (r != null)
+ {
+ // load assigned securables
+ foreach (SecurableAssignment sa in await SecurableAssignment.LoadForRole(r, _dbManager))
+ {
+ foreach (Securable s in this._securables)
+ {
+ if (sa.SecurableId == s.Id)
+ {
+ sa.AssignedSecurable = s;
+ break;
+ }
+ }
+ _assignedSecurables.Add(sa);
+ }
+ }
+ }
+
+ private async void listBoxUser_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ User? u = this.listBoxUser.SelectedItem as User;
+ this.textBoxUserFirstName.Text = (u != null) ? u.Firstname : string.Empty;
+ this.textBoxUserLastName.Text = (u != null) ? u.Lastname : string.Empty;
+ this.textBoxUserUserName.Text = (u != null) ? u.Username : string.Empty;
+ this.textBoxUserAPIKey.Text = (u != null) ? u.APIKey : string.Empty;
+ this.textBoxUserCreated.Text = (u != null) ? u.Created.ToString() : string.Empty;
+ this.textBoxUserModified.Text = (u != null) ? u.Modified.ToString() : string.Empty;
+ this.textBoxUserPassword.Text = string.Empty;
+
+ _assignedRoles.Clear();
+
+ if (u != null)
+ {
+ // load roles assigned to user
+ foreach (RoleAssignment ra in await RoleAssignment.LoadForUser(u, _dbManager))
+ {
+ foreach (Role r in this._roles)
+ {
+ if (ra.RoleId == r.Id)
+ {
+ ra.AssignedRole = r;
+ break;
+ }
+ }
+ _assignedRoles.Add(ra);
+ }
+ }
+ }
+
+ private void listBoxSecurables_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ Securable? s = this.listBoxSecurables.SelectedItem as Securable;
+ this.textBoxSecurableName.Text = (s != null) ? s.Name : string.Empty;
+ }
+
+ #endregion
+
+ #region menuitem callbacks
+
+ private async void menuItemDeleteParticipant_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if(this.listBoxParticipant.SelectedItem is Participant p)
+ {
+ await p.Delete(_dbManager);
+ this._participants.Remove(p);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void menuItemNewParticipant_Click(object sender, RoutedEventArgs e)
+ {
+ Participant p = new();
+ this._participants.Add(p);
+ this.listBoxParticipant.SelectedItem = p;
+ }
+
+ private void menuItemNewUser_Click(object sender, RoutedEventArgs e)
+ {
+ Participant? p = this.listBoxParticipant.SelectedItem as Participant;
+ if(p != null)
+ {
+ User u = new();
+ u.Participant_Id = p.Id;
+ _users.Add(u);
+ this.listBoxUser.SelectedItem = u;
+ }
+ }
+
+ private async void menuItemDeleteUser_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if (this.listBoxUser.SelectedItem is User u)
+ {
+ await u.Delete(_dbManager);
+ this._users.Remove(u);
+ }
+ }
+ catch(Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void menuItemNewRole_Click(object sender, RoutedEventArgs e)
+ {
+ Role r = new();
+ this._roles.Add(r);
+ this.listBoxRoles.SelectedItem = r;
+ }
+
+ private async void menuItemDeleteRole_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if (this.listBoxRoles.SelectedItem is Role r)
+ {
+ await r.Delete(_dbManager);
+ this._roles.Remove(r);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void menuItemNewSecurable_Click(object sender, RoutedEventArgs e)
+ {
+ Securable s = new Securable();
+ _securables.Add(s);
+ this.listBoxSecurables.SelectedItem = s;
+ }
+
+ private async void menuItemDeleteSecurable_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if (this.listBoxSecurables.SelectedItem is Securable s)
+ {
+ await s.Delete(_dbManager);
+ this._securables.Remove(s);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/src/RoleEditor/Resources.Designer.cs b/src/RoleEditor/Resources.Designer.cs
new file mode 100644
index 0000000..b62f446
--- /dev/null
+++ b/src/RoleEditor/Resources.Designer.cs
@@ -0,0 +1,173 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace RoleEditor {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RoleEditor.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] about {
+ get {
+ object obj = ResourceManager.GetObject("about", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] add {
+ get {
+ object obj = ResourceManager.GetObject("add", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] arrow_left_green {
+ get {
+ object obj = ResourceManager.GetObject("arrow_left_green", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] businessman {
+ get {
+ object obj = ResourceManager.GetObject("businessman", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] delete2 {
+ get {
+ object obj = ResourceManager.GetObject("delete2", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] disk_blue {
+ get {
+ object obj = ResourceManager.GetObject("disk_blue", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] id_card {
+ get {
+ object obj = ResourceManager.GetObject("id_card", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] key1 {
+ get {
+ object obj = ResourceManager.GetObject("key1", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] key1_add {
+ get {
+ object obj = ResourceManager.GetObject("key1_add", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] lock_preferences {
+ get {
+ object obj = ResourceManager.GetObject("lock_preferences", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] safe {
+ get {
+ object obj = ResourceManager.GetObject("safe", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+ }
+}
diff --git a/src/RoleEditor/Resources.resx b/src/RoleEditor/Resources.resx
new file mode 100644
index 0000000..2d7da8b
--- /dev/null
+++ b/src/RoleEditor/Resources.resx
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ Resources\about.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\add.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\arrow_left_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\businessman.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\delete2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\disk_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\id_card.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\key1.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\key1_add.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\lock_preferences.ico;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\safe.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/RoleEditor/Resources/about.png b/src/RoleEditor/Resources/about.png
new file mode 100644
index 0000000..c2738bf
Binary files /dev/null and b/src/RoleEditor/Resources/about.png differ
diff --git a/src/RoleEditor/Resources/add.png b/src/RoleEditor/Resources/add.png
new file mode 100644
index 0000000..a871081
Binary files /dev/null and b/src/RoleEditor/Resources/add.png differ
diff --git a/src/RoleEditor/Resources/arrow_left_green.png b/src/RoleEditor/Resources/arrow_left_green.png
new file mode 100644
index 0000000..39eb6e6
Binary files /dev/null and b/src/RoleEditor/Resources/arrow_left_green.png differ
diff --git a/src/RoleEditor/Resources/businessman.png b/src/RoleEditor/Resources/businessman.png
new file mode 100644
index 0000000..0e7499a
Binary files /dev/null and b/src/RoleEditor/Resources/businessman.png differ
diff --git a/src/RoleEditor/Resources/delete2.png b/src/RoleEditor/Resources/delete2.png
new file mode 100644
index 0000000..32a7f9e
Binary files /dev/null and b/src/RoleEditor/Resources/delete2.png differ
diff --git a/src/RoleEditor/Resources/disk_blue.png b/src/RoleEditor/Resources/disk_blue.png
new file mode 100644
index 0000000..1abc0e7
Binary files /dev/null and b/src/RoleEditor/Resources/disk_blue.png differ
diff --git a/src/RoleEditor/Resources/id_card.png b/src/RoleEditor/Resources/id_card.png
new file mode 100644
index 0000000..70e78af
Binary files /dev/null and b/src/RoleEditor/Resources/id_card.png differ
diff --git a/src/RoleEditor/Resources/key1.png b/src/RoleEditor/Resources/key1.png
new file mode 100644
index 0000000..53d2a24
Binary files /dev/null and b/src/RoleEditor/Resources/key1.png differ
diff --git a/src/RoleEditor/Resources/key1_add.png b/src/RoleEditor/Resources/key1_add.png
new file mode 100644
index 0000000..06057ef
Binary files /dev/null and b/src/RoleEditor/Resources/key1_add.png differ
diff --git a/src/RoleEditor/Resources/lock_preferences.ico b/src/RoleEditor/Resources/lock_preferences.ico
new file mode 100644
index 0000000..4128cdb
Binary files /dev/null and b/src/RoleEditor/Resources/lock_preferences.ico differ
diff --git a/src/RoleEditor/Resources/safe.png b/src/RoleEditor/Resources/safe.png
new file mode 100644
index 0000000..2bd364d
Binary files /dev/null and b/src/RoleEditor/Resources/safe.png differ
diff --git a/src/RoleEditor/RoleEditor.csproj b/src/RoleEditor/RoleEditor.csproj
new file mode 100644
index 0000000..936e6bb
--- /dev/null
+++ b/src/RoleEditor/RoleEditor.csproj
@@ -0,0 +1,61 @@
+
+
+
+ WinExe
+ net6.0-windows
+ enable
+ true
+ Resources\lock_preferences.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
diff --git a/src/RoleEditor/RoleEditor.sln b/src/RoleEditor/RoleEditor.sln
new file mode 100644
index 0000000..6b98d6e
--- /dev/null
+++ b/src/RoleEditor/RoleEditor.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33516.290
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoleEditor", "RoleEditor.csproj", "{8A8CB0D3-7728-468E-AB11-E811BA5B5BC0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "brecal.model", "..\brecal.model\brecal.model.csproj", "{F3BC5ADC-BF57-47DC-A5D5-CC4A13857DEE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "brecal.mysql", "..\brecal.mysql\brecal.mysql.csproj", "{E88F908B-48C9-46BD-A3AE-C36FBE9EDF1F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8A8CB0D3-7728-468E-AB11-E811BA5B5BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A8CB0D3-7728-468E-AB11-E811BA5B5BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A8CB0D3-7728-468E-AB11-E811BA5B5BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A8CB0D3-7728-468E-AB11-E811BA5B5BC0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3BC5ADC-BF57-47DC-A5D5-CC4A13857DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3BC5ADC-BF57-47DC-A5D5-CC4A13857DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3BC5ADC-BF57-47DC-A5D5-CC4A13857DEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3BC5ADC-BF57-47DC-A5D5-CC4A13857DEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E88F908B-48C9-46BD-A3AE-C36FBE9EDF1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E88F908B-48C9-46BD-A3AE-C36FBE9EDF1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E88F908B-48C9-46BD-A3AE-C36FBE9EDF1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E88F908B-48C9-46BD-A3AE-C36FBE9EDF1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {12E46C08-C5A8-46E8-8A8C-04BA6F197DCB}
+ EndGlobalSection
+EndGlobal
diff --git a/src/brecal.model/DbEntity.cs b/src/brecal.model/DbEntity.cs
new file mode 100644
index 0000000..e341bf7
--- /dev/null
+++ b/src/brecal.model/DbEntity.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace brecal.model
+{
+ public abstract class DbEntity
+ {
+ ///
+ /// DB primary key
+ ///
+ public uint Id { get; set; }
+
+ ///
+ /// Creation timestamp, if null record is unsaved
+ ///
+ public DateTime? Created { get; set; }
+
+ ///
+ /// Modified timestamp, if null record was never modified
+ ///
+ public DateTime? Modified { get; set; }
+
+ ///
+ /// Set query and cmd parameters for an update query
+ ///
+ /// CMD created by DB manager
+ public abstract void SetUpdate(IDbCommand cmd);
+
+ ///
+ /// set query and cmd parameters for a create query
+ ///
+ /// CMD created by DB manager
+ public abstract void SetCreate(IDbCommand cmd);
+
+ ///
+ /// set query and cmd parameters for a delete query
+ ///
+ /// CMD created by DB manager
+ public abstract void SetDelete(IDbCommand cmd);
+
+ ///
+ /// Each database entity must be able to save itself to the database
+ ///
+ public async Task Save(IDBManager manager)
+ {
+ if (this.Created.HasValue)
+ {
+ await manager.ExecuteNonQuery(this.SetUpdate);
+ }
+ else
+ {
+ this.Id = (uint)await manager.ExecuteNonQuery(this.SetCreate);
+ }
+ }
+
+ ///
+ /// Each entity must be able to delete itself
+ ///
+ public async Task Delete(IDBManager manager)
+ {
+ await manager.ExecuteNonQuery(this.SetDelete);
+ }
+
+ }
+}
diff --git a/src/brecal.model/IDBManager.cs b/src/brecal.model/IDBManager.cs
new file mode 100644
index 0000000..1fad343
--- /dev/null
+++ b/src/brecal.model/IDBManager.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace brecal.model
+{
+ public interface IDBManager
+ {
+
+ delegate List LoadFunc(T entity);
+
+ delegate void QueryFunc(IDbCommand cmd, params object?[] args);
+
+ Task> Load(QueryFunc prepareAction, LoadFunc loadAction, params object?[] args);
+
+ Task