From ffb126276f933464a650156a3e97b2fb38e41d00 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Thu, 27 Mar 2025 05:59:01 -0600 Subject: [PATCH 1/5] Add ID to reminderexportmodel. --- Controllers/APIController.cs | 2 +- Logic/VehicleLogic.cs | 4 ++-- Models/Shared/ImportModel.cs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index a5fd82d..2c319a6 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1403,7 +1403,7 @@ namespace CarCareTracker.Controllers } var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId); var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId); - var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()}); + var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()}); if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant")) { return Json(results, StaticHelper.GetInvariantOption()); diff --git a/Logic/VehicleLogic.cs b/Logic/VehicleLogic.cs index 04dc5b6..30dc2c1 100644 --- a/Logic/VehicleLogic.cs +++ b/Logic/VehicleLogic.cs @@ -303,11 +303,11 @@ namespace CarCareTracker.Logic //set next reminder if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date)) { - resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); + resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); } else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage)) { - resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); + resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); } apiResult.Add(resultToAdd); } diff --git a/Models/Shared/ImportModel.cs b/Models/Shared/ImportModel.cs index cbb424a..9533344 100644 --- a/Models/Shared/ImportModel.cs +++ b/Models/Shared/ImportModel.cs @@ -115,6 +115,8 @@ namespace CarCareTracker.Models } public class ReminderExportModel { + [JsonConverter(typeof(FromIntOptional))] + public string Id { get; set; } public string Description { get; set; } public string Urgency { get; set; } public string Metric { get; set; } From 5d3746f168c2f57cd6161e613f8274461887274f Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Sat, 26 Apr 2025 08:56:05 -0600 Subject: [PATCH 2/5] add POST api endpoint for basic reminders. --- Controllers/APIController.cs | 76 +++++++++++++++++++++++++++++++++++- Logic/VehicleLogic.cs | 4 +- Models/Shared/ImportModel.cs | 1 + 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index 3fbc865..a4a8c5b 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1516,7 +1516,7 @@ namespace CarCareTracker.Controllers } var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId); var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId); - var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()}); + var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }); if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant")) { return Json(results, StaticHelper.GetInvariantOption()); @@ -1526,6 +1526,80 @@ namespace CarCareTracker.Controllers return Json(results); } } + [TypeFilter(typeof(CollaboratorFilter))] + [HttpPost] + [Route("/api/vehicle/reminders/add")] + [Consumes("application/json")] + public IActionResult AddReminderRecordJson(int vehicleId, [FromBody] ReminderExportModel input) => AddReminderRecord(vehicleId, input); + [TypeFilter(typeof(CollaboratorFilter))] + [HttpPost] + [Route("/api/vehicle/reminders/add")] + public IActionResult AddReminderRecord(int vehicleId, ReminderExportModel input) + { + if (vehicleId == default) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Must provide a valid vehicle id")); + } + if (string.IsNullOrWhiteSpace(input.Description) || + string.IsNullOrWhiteSpace(input.Metric)) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Input object invalid, Description and Metric cannot be empty.")); + } + bool validMetric = Enum.TryParse(input.Metric, out ReminderMetric parsedMetric); + bool validDate = DateTime.TryParse(input.DueDate, out DateTime parsedDate); + bool validOdometer = int.TryParse(input.DueOdometer, out int parsedOdometer); + if (!validMetric) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Input object invalid, values for Metric(Date, Odometer, Both) is invalid.")); + } + //validate metrics + switch (parsedMetric) + { + case ReminderMetric.Both: + //validate due date and odometer + if (!validDate || !validOdometer) + { + return Json(OperationResponse.Failed("Input object invalid, DueDate and DueOdometer must be valid if Metric is Both")); + } + break; + case ReminderMetric.Date: + if (!validDate) + { + return Json(OperationResponse.Failed("Input object invalid, DueDate must be valid if Metric is Date")); + } + break; + case ReminderMetric.Odometer: + if (!validOdometer) + { + return Json(OperationResponse.Failed("Input object invalid, DueOdometer must be valid if Metric is Odometer")); + } + break; + } + try + { + var reminderRecord = new ReminderRecord() + { + VehicleId = vehicleId, + Description = input.Description, + Mileage = parsedOdometer, + Date = parsedDate, + Metric = parsedMetric, + Notes = input.Notes, + Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List() : input.Tags.Split(' ').Distinct().ToList() + }; + _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord); + StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord, "reminderrecord.add.api", User.Identity.Name)); + return Json(OperationResponse.Succeed("Reminder Record Added")); + } + catch (Exception ex) + { + Response.StatusCode = 500; + return Json(OperationResponse.Failed(ex.Message)); + } + } [HttpGet] [Route("/api/calendar")] public IActionResult Calendar() diff --git a/Logic/VehicleLogic.cs b/Logic/VehicleLogic.cs index 30dc2c1..653e641 100644 --- a/Logic/VehicleLogic.cs +++ b/Logic/VehicleLogic.cs @@ -303,11 +303,11 @@ namespace CarCareTracker.Logic //set next reminder if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date)) { - resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); + resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First(); } else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage)) { - resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First(); + resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First(); } apiResult.Add(resultToAdd); } diff --git a/Models/Shared/ImportModel.cs b/Models/Shared/ImportModel.cs index 9533344..a4e5059 100644 --- a/Models/Shared/ImportModel.cs +++ b/Models/Shared/ImportModel.cs @@ -125,6 +125,7 @@ namespace CarCareTracker.Models public string DueDate { get; set; } [JsonConverter(typeof(FromIntOptional))] public string DueOdometer { get; set; } + public string Tags { get; set; } } public class PlanRecordExportModel { From 9968ccb541ca8254fbc869fcf55102e6fa207343 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Sat, 26 Apr 2025 09:11:40 -0600 Subject: [PATCH 3/5] add put API --- Controllers/APIController.cs | 84 +++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index a4a8c5b..6ba0072 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1504,6 +1504,7 @@ namespace CarCareTracker.Controllers } } #endregion + #region ReminderRecord [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/reminders")] @@ -1587,7 +1588,7 @@ namespace CarCareTracker.Controllers Mileage = parsedOdometer, Date = parsedDate, Metric = parsedMetric, - Notes = input.Notes, + Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List() : input.Tags.Split(' ').Distinct().ToList() }; _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord); @@ -1600,6 +1601,86 @@ namespace CarCareTracker.Controllers return Json(OperationResponse.Failed(ex.Message)); } } + [HttpPut] + [Route("/api/vehicle/reminders/update")] + [Consumes("application/json")] + public IActionResult UpdateReminderRecordJson([FromBody] ReminderExportModel input) => UpdateReminderRecord(input); + [HttpPut] + [Route("/api/vehicle/reminders/update")] + public IActionResult UpdateReminderRecord(ReminderExportModel input) + { + if (string.IsNullOrWhiteSpace(input.Id) || + string.IsNullOrWhiteSpace(input.Description) || + string.IsNullOrWhiteSpace(input.Metric)) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Input object invalid, Id, Description and Metric cannot be empty.")); + } + bool validMetric = Enum.TryParse(input.Metric, out ReminderMetric parsedMetric); + bool validDate = DateTime.TryParse(input.DueDate, out DateTime parsedDate); + bool validOdometer = int.TryParse(input.DueOdometer, out int parsedOdometer); + if (!validMetric) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Input object invalid, values for Metric(Date, Odometer, Both) is invalid.")); + } + //validate metrics + switch (parsedMetric) + { + case ReminderMetric.Both: + //validate due date and odometer + if (!validDate || !validOdometer) + { + return Json(OperationResponse.Failed("Input object invalid, DueDate and DueOdometer must be valid if Metric is Both")); + } + break; + case ReminderMetric.Date: + if (!validDate) + { + return Json(OperationResponse.Failed("Input object invalid, DueDate must be valid if Metric is Date")); + } + break; + case ReminderMetric.Odometer: + if (!validOdometer) + { + return Json(OperationResponse.Failed("Input object invalid, DueOdometer must be valid if Metric is Odometer")); + } + break; + } + try + { + //retrieve existing record + var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(int.Parse(input.Id)); + if (existingRecord != null && existingRecord.Id == int.Parse(input.Id)) + { + //check if user has access to the vehicleId + if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId)) + { + Response.StatusCode = 401; + return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle.")); + } + existingRecord.Date = parsedDate; + existingRecord.Mileage = parsedOdometer; + existingRecord.Description = input.Description; + existingRecord.Metric = parsedMetric; + existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes; + existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List() : input.Tags.Split(' ').Distinct().ToList(); + _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord); + StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.update.api", User.Identity.Name)); + } + else + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Invalid Record Id")); + } + return Json(OperationResponse.Succeed("Reminder Record Updated")); + } + catch (Exception ex) + { + Response.StatusCode = 500; + return Json(OperationResponse.Failed(ex.Message)); + } + } [HttpGet] [Route("/api/calendar")] public IActionResult Calendar() @@ -1613,6 +1694,7 @@ namespace CarCareTracker.Controllers var calendarContent = StaticHelper.RemindersToCalendar(reminders); return File(calendarContent, "text/calendar"); } + #endregion [HttpPost] [Route("/api/documents/upload")] public IActionResult UploadDocument(List documents) From f307c0933a1a4557716c349df701303f6aa9f6b9 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Sat, 26 Apr 2025 09:16:38 -0600 Subject: [PATCH 4/5] add delete endpoint --- Controllers/APIController.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index 6ba0072..f56c841 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1681,6 +1681,29 @@ namespace CarCareTracker.Controllers return Json(OperationResponse.Failed(ex.Message)); } } + [HttpDelete] + [Route("/api/vehicle/reminders/delete")] + public IActionResult DeleteReminderRecord(int id) + { + var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(id); + if (existingRecord == null || existingRecord.Id == default) + { + Response.StatusCode = 400; + return Json(OperationResponse.Failed("Invalid Record Id")); + } + //security check. + if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId)) + { + Response.StatusCode = 401; + return Json(OperationResponse.Failed("Access Denied, you don't have access to this vehicle.")); + } + var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id); + if (result) + { + StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete.api", User.Identity.Name)); + } + return Json(OperationResponse.Conditional(result, "Reminder Record Deleted")); + } [HttpGet] [Route("/api/calendar")] public IActionResult Calendar() From b6d6a8765d171d407065f08b77954a5cf71b5447 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Sat, 26 Apr 2025 09:32:15 -0600 Subject: [PATCH 5/5] update API documentation --- Views/API/Index.cshtml | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Views/API/Index.cshtml b/Views/API/Index.cshtml index 8477a4e..8a71a68 100644 --- a/Views/API/Index.cshtml +++ b/Views/API/Index.cshtml @@ -669,6 +669,65 @@ vehicleId - Id of Vehicle +
+
+ POST +
+
+ /api/vehicle/reminders/add +
+
+ Adds Reminder Record to the vehicle +
+
+ vehicleId - Id of Vehicle +
+ Body(form-data): {
+ description - Description
+ dueDate - Due Date
+ dueOdometer - Due Odometer reading
+ metric - Date/Odometer/Both
+ notes - notes(optional)
+ tags - tags separated by space(optional)
+ } +
+
+
+
+ PUT +
+
+ /api/vehicle/reminders/update +
+
+ Updates Reminder Record +
+
+ Body(form-data): {
+ Id - Id of Reminder Record
+ description - Description
+ dueDate - Due Date
+ dueOdometer - Due Odometer reading
+ metric - Date/Odometer/Both
+ notes - notes(optional)
+ tags - tags separated by space(optional)
+ } +
+
+
+
+ DELETE +
+
+ /api/vehicle/reminders/delete +
+
+ Deletes Reminder Record +
+
+ Id - Id of Reminder Record +
+
GET