diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index 6e911b1..f2d660c 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -72,7 +72,11 @@ namespace CarCareTracker.Controllers [HttpPost] public IActionResult DeleteUser(int userId) { - var result =_userLogic.DeleteAllAccessToUser(userId) && _configHelper.DeleteUserConfig(userId) && _loginLogic.DeleteUser(userId); + var result =_userLogic.DeleteAllAccessToUser(userId) + && _configHelper.DeleteUserConfig(userId) + && _loginLogic.DeleteUser(userId) + && _userLogic.DeleteAllHouseholdByChildUserId(userId) + && _userLogic.DeleteAllHouseholdByParentUserId(userId); return Json(result); } [HttpPost] diff --git a/Controllers/MigrationController.cs b/Controllers/MigrationController.cs index a66d95f..8acb149 100644 --- a/Controllers/MigrationController.cs +++ b/Controllers/MigrationController.cs @@ -54,7 +54,8 @@ namespace CarCareTracker.Controllers "CREATE TABLE IF NOT EXISTS app.useraccessrecords (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))", "CREATE TABLE IF NOT EXISTS app.extrafields (id INT primary key, data jsonb not null)", "CREATE TABLE IF NOT EXISTS app.inspectionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)", - "CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)" + "CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)", + "CREATE TABLE IF NOT EXISTS app.userhouseholdrecords (parentUserId INT, childUserId INT, PRIMARY KEY(parentUserId, childUserId))" }; foreach(string cmd in cmds) { @@ -103,6 +104,7 @@ namespace CarCareTracker.Controllers var extrafields = new List(); var inspectionrecords = new List(); var inspectionrecordtemplates = new List(); + var userhouseholdrecords = new List(); #region "Part1" string cmd = $"SELECT data FROM app.vehicles"; using (var ctext = pgDataSource.CreateCommand(cmd)) @@ -455,6 +457,32 @@ namespace CarCareTracker.Controllers table.Upsert(record); }; } + cmd = $"SELECT parentUserId, childUserId FROM app.userhouseholdrecords"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + using (NpgsqlDataReader reader = ctext.ExecuteReader()) + while (reader.Read()) + { + UserHousehold result = new UserHousehold() + { + Id = new HouseholdAccess + { + ParentUserId = int.Parse(reader["parentUserId"].ToString()), + ChildUserId = int.Parse(reader["childUserId"].ToString()) + } + }; + userhouseholdrecords.Add(result); + } + } + foreach (var record in userhouseholdrecords) + { + using (var db = new LiteDatabase(fullFileName)) + { + var table = db.GetCollection("userhouseholdrecords"); + table.Upsert(record); + } + ; + } #endregion var destFilePath = $"{fullFolderPath}.zip"; ZipFile.CreateFromDirectory(fullFolderPath, destFilePath); @@ -505,6 +533,7 @@ namespace CarCareTracker.Controllers var extrafields = new List(); var inspectionrecords = new List(); var inspectionrecordtemplates = new List(); + var userhouseholdrecords = new List(); #region "Part1" using (var db = new LiteDatabase(fullFileName)) { @@ -816,6 +845,22 @@ namespace CarCareTracker.Controllers ctext.ExecuteNonQuery(); } } + using (var db = new LiteDatabase(fullFileName)) + { + var table = db.GetCollection("userhouseholdrecords"); + userhouseholdrecords = table.FindAll().ToList(); + } + ; + foreach (var record in userhouseholdrecords) + { + string cmd = $"INSERT INTO app.userhouseholdrecords (parentUserId, childUserId) VALUES(@parentUserId, @childUserId)"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", record.Id.ParentUserId); + ctext.Parameters.AddWithValue("childUserId", record.Id.ChildUserId); + ctext.ExecuteNonQuery(); + } + } #endregion return Json(OperationResponse.Succeed("Data Imported Successfully")); } diff --git a/External/Implementations/Litedb/UserAccessDataAcces.cs b/External/Implementations/Litedb/UserAccessDataAcces.cs index 15fa08e..6dedc74 100644 --- a/External/Implementations/Litedb/UserAccessDataAcces.cs +++ b/External/Implementations/Litedb/UserAccessDataAcces.cs @@ -65,7 +65,7 @@ namespace CarCareTracker.External.Implementations return true; } /// - /// Delee all access records when a user is deleted. + /// Delete all access records when a user is deleted. /// /// /// diff --git a/External/Implementations/Litedb/UserHouseholdDataAcces.cs b/External/Implementations/Litedb/UserHouseholdDataAcces.cs new file mode 100644 index 0000000..e0d530a --- /dev/null +++ b/External/Implementations/Litedb/UserHouseholdDataAcces.cs @@ -0,0 +1,76 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; + +namespace CarCareTracker.External.Implementations +{ + public class UserHouseholdDataAccess : IUserHouseholdDataAccess + { + private ILiteDBHelper _liteDB { get; set; } + private static string tableName = "userhouseholdrecords"; + public UserHouseholdDataAccess(ILiteDBHelper liteDB) + { + _liteDB = liteDB; + } + public List GetUserHouseholdByParentUserId(int parentUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + return table.Find(x => x.Id.ParentUserId == parentUserId).ToList(); + } + public List GetUserHouseholdByChildUserId(int childUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + return table.Find(x => x.Id.ChildUserId == childUserId).ToList(); + } + public UserHousehold GetUserHouseholdByParentAndChildUserId(int parentUserId, int childUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + return table.Find(x => x.Id.ParentUserId == parentUserId && x.Id.ChildUserId == childUserId).FirstOrDefault(); + } + public bool SaveUserHousehold(UserHousehold userHousehold) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + table.Upsert(userHousehold); + db.Checkpoint(); + return true; + } + public bool DeleteUserHousehold(int parentUserId, int childUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + table.DeleteMany(x => x.Id.ParentUserId == parentUserId && x.Id.ChildUserId == childUserId); + db.Checkpoint(); + return true; + } + /// + /// Delete all household records when a parent user is deleted. + /// + /// + /// + public bool DeleteAllHouseholdRecordsByParentUserId(int parentUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + table.DeleteMany(x => x.Id.ParentUserId == parentUserId); + db.Checkpoint(); + return true; + } + /// + /// Delete all household records when a child user is deleted. + /// + /// + /// + public bool DeleteAllHouseholdRecordsByChildUserId(int childUserId) + { + var db = _liteDB.GetLiteDB(); + var table = db.GetCollection(tableName); + table.DeleteMany(x => x.Id.ChildUserId == childUserId); + db.Checkpoint(); + return true; + } + } +} \ No newline at end of file diff --git a/External/Implementations/Postgres/UserHouseholdDataAccess.cs b/External/Implementations/Postgres/UserHouseholdDataAccess.cs new file mode 100644 index 0000000..74b6337 --- /dev/null +++ b/External/Implementations/Postgres/UserHouseholdDataAccess.cs @@ -0,0 +1,207 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Models; +using Npgsql; + +namespace CarCareTracker.External.Implementations +{ + public class PGUserHouseholdDataAccess : IUserHouseholdDataAccess + { + private NpgsqlDataSource pgDataSource; + private readonly ILogger _logger; + private static string tableName = "userhouseholdrecords"; + public PGUserHouseholdDataAccess(IConfiguration config, ILogger logger) + { + pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]); + _logger = logger; + try + { + //create table if not exist. + string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (parentUserId INT, childUserId INT, PRIMARY KEY(parentUserId, childUserId))"; + using (var ctext = pgDataSource.CreateCommand(initCMD)) + { + ctext.ExecuteNonQuery(); + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + } + public List GetUserHouseholdByParentUserId(int parentUserId) + { + try + { + string cmd = $"SELECT parentUserId, childUserId FROM app.{tableName} WHERE parentUserId = @parentUserId"; + var results = new List(); + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", parentUserId); + using (NpgsqlDataReader reader = ctext.ExecuteReader()) + while (reader.Read()) + { + UserHousehold result = new UserHousehold() + { + Id = new HouseholdAccess + { + ParentUserId = int.Parse(reader["parentUserId"].ToString()), + ChildUserId = int.Parse(reader["childUserId"].ToString()) + } + }; + results.Add(result); + } + } + return results; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return new List(); + } + } + public List GetUserHouseholdByChildUserId(int childUserId) + { + try + { + string cmd = $"SELECT parentUserId, childUserId FROM app.{tableName} WHERE childUserId = @childUserId"; + var results = new List(); + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("childUserId", childUserId); + using (NpgsqlDataReader reader = ctext.ExecuteReader()) + while (reader.Read()) + { + UserHousehold result = new UserHousehold() + { + Id = new HouseholdAccess + { + ParentUserId = int.Parse(reader["parentUserId"].ToString()), + ChildUserId = int.Parse(reader["childUserId"].ToString()) + } + }; + results.Add(result); + } + } + return results; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return new List(); + } + } + public UserHousehold GetUserHouseholdByParentAndChildUserId(int parentUserId, int childUserId) + { + try + { + string cmd = $"SELECT parentUserId, childUserId FROM app.{tableName} WHERE parentUserId = @parentUserId AND childUserId = @childUserId"; + UserHousehold result = null; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", parentUserId); + ctext.Parameters.AddWithValue("childUserId", childUserId); + using (NpgsqlDataReader reader = ctext.ExecuteReader()) + while (reader.Read()) + { + result = new UserHousehold() + { + Id = new HouseholdAccess + { + ParentUserId = int.Parse(reader["parentUserId"].ToString()), + ChildUserId = int.Parse(reader["childUserId"].ToString()) + } + }; + return result; + } + } + return result; + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return new UserHousehold(); + } + } + public bool SaveUserHousehold(UserHousehold userHousehold) + { + try + { + string cmd = $"INSERT INTO app.{tableName} (parentUserId, childUserId) VALUES(@parentUserId, @childUserId)"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", userHousehold.Id.ParentUserId); + ctext.Parameters.AddWithValue("childUserId", userHousehold.Id.ChildUserId); + return ctext.ExecuteNonQuery() > 0; + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + public bool DeleteUserHousehold(int parentUserId, int childUserId) + { + try + { + string cmd = $"DELETE FROM app.{tableName} WHERE parentUserId = @parentUserId AND childUserId = @childUserId"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", parentUserId); + ctext.Parameters.AddWithValue("childUserId", childUserId); + return ctext.ExecuteNonQuery() > 0; + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + /// + /// Delete all household records when a parent user is deleted. + /// + /// + /// + public bool DeleteAllHouseholdRecordsByParentUserId(int parentUserId) + { + try + { + string cmd = $"DELETE FROM app.{tableName} WHERE parentUserId = @parentUserId"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("parentUserId", parentUserId); + ctext.ExecuteNonQuery(); + return true; + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + /// + /// Delete all household records when a child user is deleted. + /// + /// + /// + public bool DeleteAllHouseholdRecordsByChildUserId(int childUserId) + { + try + { + string cmd = $"DELETE FROM app.{tableName} WHERE childUserId = @childUserId"; + using (var ctext = pgDataSource.CreateCommand(cmd)) + { + ctext.Parameters.AddWithValue("childUserId", childUserId); + ctext.ExecuteNonQuery(); + return true; + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + return false; + } + } + } +} \ No newline at end of file diff --git a/External/Interfaces/IUserHouseholdDataAccess.cs b/External/Interfaces/IUserHouseholdDataAccess.cs new file mode 100644 index 0000000..585bf3f --- /dev/null +++ b/External/Interfaces/IUserHouseholdDataAccess.cs @@ -0,0 +1,15 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IUserHouseholdDataAccess + { + List GetUserHouseholdByParentUserId(int parentUserId); + List GetUserHouseholdByChildUserId(int childUserId); + UserHousehold GetUserHouseholdByParentAndChildUserId(int parentUserId, int childUserId); + bool SaveUserHousehold(UserHousehold userHousehold); + bool DeleteUserHousehold(int parentUserId, int childUserId); + bool DeleteAllHouseholdRecordsByParentUserId(int parentUserId); + bool DeleteAllHouseholdRecordsByChildUserId(int childUserId); + } +} \ No newline at end of file diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index 59a6f9f..451987c 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -1,5 +1,4 @@ using CarCareTracker.External.Interfaces; -using CarCareTracker.Helper; using CarCareTracker.Models; namespace CarCareTracker.Logic @@ -15,15 +14,22 @@ namespace CarCareTracker.Logic bool UserCanEditVehicle(int userId, int vehicleId); bool DeleteAllAccessToVehicle(int vehicleId); bool DeleteAllAccessToUser(int userId); + OperationResponse AddUserToHousehold(int parentUserId, string childUsername); + bool DeleteUserFromHousehold(int parentUserId, int childUserId); + bool DeleteAllHouseholdByParentUserId(int parentUserId); + bool DeleteAllHouseholdByChildUserId(int childUserId); } public class UserLogic: IUserLogic { private readonly IUserAccessDataAccess _userAccess; private readonly IUserRecordDataAccess _userData; + private readonly IUserHouseholdDataAccess _userHouseholdData; public UserLogic(IUserAccessDataAccess userAccess, - IUserRecordDataAccess userData) { + IUserRecordDataAccess userData, + IUserHouseholdDataAccess userHouseholdData) { _userAccess = userAccess; _userData = userData; + _userHouseholdData = userHouseholdData; } public List GetCollaboratorsForVehicle(int vehicleId) { @@ -108,10 +114,24 @@ namespace CarCareTracker.Logic { return results; } - var accessibleVehicles = _userAccess.GetUserAccessByUserId(userId); - if (accessibleVehicles.Any()) + List userIds = new List { userId }; + List vehicleIds = new List(); + var userHouseholds = _userHouseholdData.GetUserHouseholdByChildUserId(userId); + if (userHouseholds.Any()) + { + //add parent's user ids + userIds.AddRange(userHouseholds.Select(x => x.Id.ParentUserId)); + } + foreach(int userIdToCheck in userIds) + { + var accessibleVehicles = _userAccess.GetUserAccessByUserId(userIdToCheck); + if (accessibleVehicles.Any()) + { + vehicleIds.AddRange(accessibleVehicles.Select(x => x.Id.VehicleId)); + } + } + if (vehicleIds.Any()) { - var vehicleIds = accessibleVehicles.Select(x => x.Id.VehicleId); return results.Where(x => vehicleIds.Contains(x.Id)).ToList(); } else @@ -125,10 +145,20 @@ namespace CarCareTracker.Logic { return true; } - var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); - if (userAccess != null) + List userIds = new List { userId }; + var userHouseholds = _userHouseholdData.GetUserHouseholdByChildUserId(userId); + if (userHouseholds.Any()) { - return true; + //add parent's user ids + userIds.AddRange(userHouseholds.Select(x => x.Id.ParentUserId)); + } + foreach (int userIdToCheck in userIds) + { + var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userIdToCheck, vehicleId); + if (userAccess != null && userAccess.Id.UserId == userIdToCheck && userAccess.Id.VehicleId == vehicleId) + { + return true; + } } return false; } @@ -142,5 +172,53 @@ namespace CarCareTracker.Logic var result = _userAccess.DeleteAllAccessRecordsByUserId(userId); return result; } + public OperationResponse AddUserToHousehold(int parentUserId, string childUsername) + { + //attempting to add to root user + if (parentUserId == -1) + { + return OperationResponse.Failed("Root user household not allwoed"); + } + //try to find existing user. + var existingUser = _userData.GetUserRecordByUserName(childUsername); + if (existingUser.Id != default) + { + //user exists. + //check if user already belongs to the household + var householdAccess = _userHouseholdData.GetUserHouseholdByParentAndChildUserId(parentUserId, existingUser.Id); + if (householdAccess != null && householdAccess.Id.ChildUserId == existingUser.Id && householdAccess.Id.ParentUserId == parentUserId) + { + return OperationResponse.Failed("User already belongs to this household"); + } + //check if a circular dependency will exist + var circularHouseholdAccess = _userHouseholdData.GetUserHouseholdByParentAndChildUserId(existingUser.Id, parentUserId); + if (circularHouseholdAccess != null && circularHouseholdAccess.Id.ChildUserId == existingUser.Id && circularHouseholdAccess.Id.ParentUserId == parentUserId) + { + return OperationResponse.Failed("Circular dependency is not allowed"); + } + var result = _userHouseholdData.SaveUserHousehold(new UserHousehold { Id = new HouseholdAccess { ParentUserId = parentUserId, ChildUserId = existingUser.Id} }); + if (result) + { + return OperationResponse.Succeed("User Added to Household"); + } + return OperationResponse.Failed(); + } + return OperationResponse.Failed($"Unable to find user {childUsername} in the system"); + } + public bool DeleteUserFromHousehold(int parentUserId, int childUserId) + { + var result = _userHouseholdData.DeleteUserHousehold(parentUserId, childUserId); + return result; + } + public bool DeleteAllHouseholdByParentUserId(int parentUserId) + { + var result = _userHouseholdData.DeleteAllHouseholdRecordsByParentUserId(parentUserId); + return result; + } + public bool DeleteAllHouseholdByChildUserId(int childUserId) + { + var result = _userHouseholdData.DeleteAllHouseholdRecordsByChildUserId(childUserId); + return result; + } } } diff --git a/Models/User/UserHousehold.cs b/Models/User/UserHousehold.cs new file mode 100644 index 0000000..74c39a8 --- /dev/null +++ b/Models/User/UserHousehold.cs @@ -0,0 +1,12 @@ +namespace CarCareTracker.Models +{ + public class HouseholdAccess + { + public int ParentUserId { get; set; } + public int ChildUserId { get; set; } + } + public class UserHousehold + { + public HouseholdAccess Id { get; set; } + } +} diff --git a/Program.cs b/Program.cs index 68a6be3..b11db61 100644 --- a/Program.cs +++ b/Program.cs @@ -60,6 +60,7 @@ if (!string.IsNullOrWhiteSpace(builder.Configuration["POSTGRES_CONNECTION"])){ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } else { @@ -82,6 +83,7 @@ else builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } //configure helpers