Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e5c9c96b4 | ||
|
|
487f64e459 | ||
|
|
d67aeaa333 | ||
|
|
fb5cdfce29 | ||
|
|
6e5cfde2cf | ||
|
|
7122f4ac0d | ||
|
|
2fb24b8b65 | ||
|
|
6540d96d4d | ||
|
|
62e196b97d | ||
|
|
4975861710 | ||
|
|
8d74799099 | ||
|
|
c58b4552b2 | ||
|
|
8c6920afab | ||
|
|
b77fd2c1c7 | ||
|
|
2035d6f6e2 | ||
|
|
e58454ef5d | ||
|
|
915eb1722d | ||
|
|
4f706d3e93 | ||
|
|
d80f0dcb8f | ||
|
|
2ae334d06d | ||
|
|
4388df71f3 | ||
|
|
c972f9c8a2 | ||
|
|
90fa6ad5fc | ||
|
|
a1b2b40abe | ||
|
|
00fd499805 | ||
|
|
8f3f71772b | ||
|
|
08104eef2a | ||
|
|
8d989ee81c | ||
|
|
2247b1b1db | ||
|
|
249ad938f6 | ||
|
|
c9d60910e5 | ||
|
|
03b89786ec | ||
|
|
8815009b04 | ||
|
|
bb4a8f7f83 | ||
|
|
c7730d1775 | ||
|
|
b7a3ef0fa7 | ||
|
|
d525e2195c | ||
|
|
cb73be0e43 | ||
|
|
a06bdbff88 | ||
|
|
0de6ab7547 | ||
|
|
5b54b8113e | ||
|
|
4d804803a8 | ||
|
|
2434245c84 | ||
|
|
d00e6e252d | ||
|
|
e006c158fc | ||
|
|
75d610200b | ||
|
|
f696030ac2 | ||
|
|
20190642a8 | ||
|
|
5d2068bd78 | ||
|
|
3a01944e30 | ||
|
|
014b6bce8d | ||
|
|
8f69399f1b | ||
|
|
200aa79dac | ||
|
|
dfb6f69d69 | ||
|
|
cd2fb2dd21 | ||
|
|
a396eb4f72 | ||
|
|
46675c944f | ||
|
|
ff7ad04f0b | ||
|
|
d69ede1447 | ||
|
|
b477cf9d51 | ||
|
|
ad8c27a2e6 | ||
|
|
52f9ae7ea1 | ||
|
|
ed1066662e | ||
|
|
e3bf6f03d7 | ||
|
|
8cc2ceecc6 | ||
|
|
9837172774 | ||
|
|
fba2a6dc68 | ||
|
|
a467ba08c6 | ||
|
|
7bfbb49efb | ||
|
|
7e4f5807d2 | ||
|
|
d5aee08f69 | ||
|
|
080d50c6bc | ||
|
|
3105622d63 | ||
|
|
eac181ff20 | ||
|
|
2a732cb343 | ||
|
|
da5e97143e | ||
|
|
9bdce69a2a | ||
|
|
8b1db34860 | ||
|
|
3c67d37e12 | ||
|
|
b73c2e979f | ||
|
|
a52fc29067 | ||
|
|
c6774d4be7 | ||
|
|
1271b3517d | ||
|
|
96914b3db4 | ||
|
|
7ad6d3369d | ||
|
|
0ae28d436b | ||
|
|
4575cf338f | ||
|
|
c526a9f207 | ||
|
|
1a3282c1ef | ||
|
|
f52e5b49c7 | ||
|
|
507fb6daf6 | ||
|
|
9d3fbd05fc | ||
|
|
2a0f884e89 | ||
|
|
26012bf27a | ||
|
|
3fa3dbaa8c | ||
|
|
95cac60c71 | ||
|
|
7878ce65ca | ||
|
|
c6c57a5de6 | ||
|
|
2ece3cd113 | ||
|
|
9504674933 | ||
|
|
3c0bdcea0e | ||
|
|
a4762d5b32 | ||
|
|
dc18bb5b8f | ||
|
|
452248e681 | ||
|
|
a7ea03014f | ||
|
|
cb89ca3426 | ||
|
|
760f7e1888 | ||
|
|
ba149568a5 | ||
|
|
2ef281bd0a | ||
|
|
859796cfe8 | ||
|
|
f1e0254f95 | ||
|
|
70d0827432 | ||
|
|
851a7af287 | ||
|
|
e46be7ba0a | ||
|
|
b4258dc11a | ||
|
|
0f35621846 | ||
|
|
374f919296 | ||
|
|
33b824b316 | ||
|
|
418e468f14 | ||
|
|
0a1f6a569d | ||
|
|
2a842d1c8c | ||
|
|
4f76991840 | ||
|
|
0487feb35e | ||
|
|
70308ed6eb | ||
|
|
bfdef5d296 | ||
|
|
a236c4a151 | ||
|
|
54d20b5573 | ||
|
|
ecd2b83cf0 | ||
|
|
80504e71c9 | ||
|
|
fb272c9c40 | ||
|
|
1c31477c37 | ||
|
|
2ecd286aa1 | ||
|
|
f28af456b3 | ||
|
|
c05b5e4c3d | ||
|
|
0f70f8212b | ||
|
|
f805e311b0 | ||
|
|
42f7bd298c | ||
|
|
e64d4f75b5 | ||
|
|
b972d5b7e5 | ||
|
|
f639c2c38b | ||
|
|
4e92155f5b | ||
|
|
78857c1b79 | ||
|
|
4e5d893850 | ||
|
|
a1fe446c2a | ||
|
|
f126a309f0 | ||
|
|
b2b129389a | ||
|
|
f2386fc9d8 | ||
|
|
d625a91ed7 | ||
|
|
b5f8d2d44e | ||
|
|
206b053d27 | ||
|
|
a2d16d7643 | ||
|
|
89345fd8eb | ||
|
|
4abf7fbba2 | ||
|
|
b5987927be | ||
|
|
bfef9b9498 | ||
|
|
6b4bbd8410 | ||
|
|
05f89073cd | ||
|
|
3776d6e11f | ||
|
|
2e301760f4 | ||
|
|
2817b5914f | ||
|
|
8276d8d720 | ||
|
|
711c7c14f7 | ||
|
|
f651f53ee6 | ||
|
|
c00ea88f73 | ||
|
|
583c83643a | ||
|
|
0775ede344 | ||
|
|
bf8ecdbe7a | ||
|
|
ed81d53175 | ||
|
|
4543e56c61 | ||
|
|
401724b66b | ||
|
|
b0773f5102 | ||
|
|
cff9289c39 | ||
|
|
69cfac1c6f | ||
|
|
0b05315671 | ||
|
|
af9c96c002 | ||
|
|
f1d4973f59 | ||
|
|
25db16c47f | ||
|
|
1286974fb6 | ||
|
|
997c045312 | ||
|
|
71c2e64daf | ||
|
|
1c2368f5a1 | ||
|
|
208eb22b8c | ||
|
|
25e32589d4 | ||
|
|
1727f0d193 | ||
|
|
0faffc2167 | ||
|
|
999649a095 | ||
|
|
cfb8e6ea55 |
8
.env
Normal file
8
.env
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
LC_ALL=en_US.UTF-8
|
||||||
|
LANG=en_US.UTF-8
|
||||||
|
MailConfig__EmailServer=""
|
||||||
|
MailConfig__EmailFrom=""
|
||||||
|
MailConfig__UseSSL=""
|
||||||
|
MailConfig__Port=587
|
||||||
|
MailConfig__Username=""
|
||||||
|
MailConfig__Password=""
|
||||||
31
.github/workflows/docker-image.yml
vendored
Normal file
31
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Docker Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: "hargata"
|
||||||
|
password: "${{ secrets.GHCR_PAT }}"
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
ghcr.io/hargata/lubelogger:latest
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ bin/
|
|||||||
obj/
|
obj/
|
||||||
wwwroot/images/
|
wwwroot/images/
|
||||||
cartracker.db
|
cartracker.db
|
||||||
|
data/cartracker.db
|
||||||
wwwroot/documents/
|
wwwroot/documents/
|
||||||
wwwroot/temp/
|
wwwroot/temp/
|
||||||
wwwroot/imports/
|
wwwroot/imports/
|
||||||
|
|||||||
149
Controllers/APIController.cs
Normal file
149
Controllers/APIController.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
public class APIController : Controller
|
||||||
|
{
|
||||||
|
private readonly IVehicleDataAccess _dataAccess;
|
||||||
|
private readonly INoteDataAccess _noteDataAccess;
|
||||||
|
private readonly IServiceRecordDataAccess _serviceRecordDataAccess;
|
||||||
|
private readonly IGasRecordDataAccess _gasRecordDataAccess;
|
||||||
|
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
|
||||||
|
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||||
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
|
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||||
|
private readonly IReminderHelper _reminderHelper;
|
||||||
|
private readonly IGasHelper _gasHelper;
|
||||||
|
private readonly IUserLogic _userLogic;
|
||||||
|
public APIController(IVehicleDataAccess dataAccess,
|
||||||
|
IGasHelper gasHelper,
|
||||||
|
IReminderHelper reminderHelper,
|
||||||
|
INoteDataAccess noteDataAccess,
|
||||||
|
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||||
|
IGasRecordDataAccess gasRecordDataAccess,
|
||||||
|
ICollisionRecordDataAccess collisionRecordDataAccess,
|
||||||
|
ITaxRecordDataAccess taxRecordDataAccess,
|
||||||
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
|
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
||||||
|
IUserLogic userLogic)
|
||||||
|
{
|
||||||
|
_dataAccess = dataAccess;
|
||||||
|
_noteDataAccess = noteDataAccess;
|
||||||
|
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||||
|
_gasRecordDataAccess = gasRecordDataAccess;
|
||||||
|
_collisionRecordDataAccess = collisionRecordDataAccess;
|
||||||
|
_taxRecordDataAccess = taxRecordDataAccess;
|
||||||
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
|
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||||
|
_gasHelper = gasHelper;
|
||||||
|
_reminderHelper = reminderHelper;
|
||||||
|
_userLogic = userLogic;
|
||||||
|
}
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
private int GetUserID()
|
||||||
|
{
|
||||||
|
return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicles")]
|
||||||
|
public IActionResult Vehicles()
|
||||||
|
{
|
||||||
|
var result = _dataAccess.GetVehicles();
|
||||||
|
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
result = _userLogic.FilterUserVehicles(result, GetUserID());
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/servicerecords")]
|
||||||
|
public IActionResult ServiceRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/repairrecords")]
|
||||||
|
public IActionResult RepairRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/upgraderecords")]
|
||||||
|
public IActionResult UpgradeRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/taxrecords")]
|
||||||
|
public IActionResult TaxRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/gasrecords")]
|
||||||
|
public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG).Select(x => new GasRecordExportModel { Date = x.Date, Odometer = x.Mileage.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString()});
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/reminders")]
|
||||||
|
public IActionResult Reminders(int vehicleId)
|
||||||
|
{
|
||||||
|
var currentMileage = 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});
|
||||||
|
return Json(results);
|
||||||
|
}
|
||||||
|
private int GetMaxMileage(int vehicleId)
|
||||||
|
{
|
||||||
|
var numbersArray = new List<int>();
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
if (serviceRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(serviceRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
if (repairRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(repairRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
if (gasRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(gasRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (upgradeRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Controllers/AdminController.cs
Normal file
56
Controllers/AdminController.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Mail;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
[Authorize(Roles = nameof(UserData.IsAdmin))]
|
||||||
|
public class AdminController : Controller
|
||||||
|
{
|
||||||
|
private ILoginLogic _loginLogic;
|
||||||
|
private IUserLogic _userLogic;
|
||||||
|
private IConfigHelper _configHelper;
|
||||||
|
public AdminController(ILoginLogic loginLogic, IUserLogic userLogic, IConfigHelper configHelper)
|
||||||
|
{
|
||||||
|
_loginLogic = loginLogic;
|
||||||
|
_userLogic = userLogic;
|
||||||
|
_configHelper = configHelper;
|
||||||
|
}
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
var viewModel = new AdminViewModel
|
||||||
|
{
|
||||||
|
Users = _loginLogic.GetAllUsers(),
|
||||||
|
Tokens = _loginLogic.GetAllTokens()
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteToken(int tokenId)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.DeleteUserToken(tokenId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteUser(int userId)
|
||||||
|
{
|
||||||
|
var result =_userLogic.DeleteAllAccessToUser(userId) && _configHelper.DeleteUserConfig(userId) && _loginLogic.DeleteUser(userId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult UpdateUserAdminStatus(int userId, bool isAdmin)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.MakeUserAdmin(userId, isAdmin);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Controllers/Error.cs
Normal file
12
Controllers/Error.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public class ErrorController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Unauthorized()
|
||||||
|
{
|
||||||
|
return View("401");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using static System.Net.Mime.MediaTypeNames;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
|
|
||||||
namespace CarCareTracker.Controllers
|
namespace CarCareTracker.Controllers
|
||||||
{
|
{
|
||||||
@@ -17,17 +14,23 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ILogger<HomeController> _logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
private readonly IVehicleDataAccess _dataAccess;
|
private readonly IVehicleDataAccess _dataAccess;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IUserLogic _userLogic;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfigHelper _config;
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger, IVehicleDataAccess dataAccess, IFileHelper fileHelper, IConfiguration configuration)
|
public HomeController(ILogger<HomeController> logger,
|
||||||
|
IVehicleDataAccess dataAccess,
|
||||||
|
IUserLogic userLogic,
|
||||||
|
IConfigHelper configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
_fileHelper = fileHelper;
|
|
||||||
_config = configuration;
|
_config = configuration;
|
||||||
|
_userLogic = userLogic;
|
||||||
|
}
|
||||||
|
private int GetUserID()
|
||||||
|
{
|
||||||
|
return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Index(string tab = "garage")
|
public IActionResult Index(string tab = "garage")
|
||||||
{
|
{
|
||||||
return View(model: tab);
|
return View(model: tab);
|
||||||
@@ -35,46 +38,22 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult Garage()
|
public IActionResult Garage()
|
||||||
{
|
{
|
||||||
var vehiclesStored = _dataAccess.GetVehicles();
|
var vehiclesStored = _dataAccess.GetVehicles();
|
||||||
|
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
|
}
|
||||||
return PartialView("_GarageDisplay", vehiclesStored);
|
return PartialView("_GarageDisplay", vehiclesStored);
|
||||||
}
|
}
|
||||||
public IActionResult Settings()
|
public IActionResult Settings()
|
||||||
{
|
{
|
||||||
var userConfig = new UserConfig
|
var userConfig = _config.GetUserConfig(User);
|
||||||
{
|
|
||||||
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
|
|
||||||
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
|
|
||||||
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
|
|
||||||
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
|
|
||||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)])
|
|
||||||
};
|
|
||||||
return PartialView("_Settings", userConfig);
|
return PartialView("_Settings", userConfig);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult WriteToSettings(UserConfig userConfig)
|
public IActionResult WriteToSettings(UserConfig userConfig)
|
||||||
{
|
{
|
||||||
try
|
var result = _config.SaveUserConfig(User, userConfig);
|
||||||
{
|
return Json(result);
|
||||||
var configFileContents = System.IO.File.ReadAllText("userConfig.json");
|
|
||||||
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
|
||||||
if (existingUserConfig is not null)
|
|
||||||
{
|
|
||||||
//copy over settings that are off limits on the settings page.
|
|
||||||
userConfig.EnableAuth = existingUserConfig.EnableAuth;
|
|
||||||
userConfig.UserNameHash = existingUserConfig.UserNameHash;
|
|
||||||
userConfig.UserPasswordHash = existingUserConfig.UserPasswordHash;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
userConfig.EnableAuth = false;
|
|
||||||
userConfig.UserNameHash = string.Empty;
|
|
||||||
userConfig.UserPasswordHash = string.Empty;
|
|
||||||
}
|
|
||||||
System.IO.File.WriteAllText("userConfig.json", System.Text.Json.JsonSerializer.Serialize(userConfig));
|
|
||||||
return Json(true);
|
|
||||||
} catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error on saving config file.");
|
|
||||||
}
|
|
||||||
return Json(false);
|
|
||||||
}
|
}
|
||||||
public IActionResult Privacy()
|
public IActionResult Privacy()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -12,19 +14,34 @@ namespace CarCareTracker.Controllers
|
|||||||
public class LoginController : Controller
|
public class LoginController : Controller
|
||||||
{
|
{
|
||||||
private IDataProtector _dataProtector;
|
private IDataProtector _dataProtector;
|
||||||
|
private ILoginLogic _loginLogic;
|
||||||
private readonly ILogger<LoginController> _logger;
|
private readonly ILogger<LoginController> _logger;
|
||||||
public LoginController(
|
public LoginController(
|
||||||
ILogger<LoginController> logger,
|
ILogger<LoginController> logger,
|
||||||
IDataProtectionProvider securityProvider
|
IDataProtectionProvider securityProvider,
|
||||||
|
ILoginLogic loginLogic
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_dataProtector = securityProvider.CreateProtector("login");
|
_dataProtector = securityProvider.CreateProtector("login");
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_loginLogic = loginLogic;
|
||||||
}
|
}
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
public IActionResult Registration()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
public IActionResult ForgotPassword()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
public IActionResult ResetPassword()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Login(LoginModel credentials)
|
public IActionResult Login(LoginModel credentials)
|
||||||
{
|
{
|
||||||
@@ -36,30 +53,18 @@ namespace CarCareTracker.Controllers
|
|||||||
//compare it against hashed credentials
|
//compare it against hashed credentials
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var configFileContents = System.IO.File.ReadAllText("userConfig.json");
|
var userData = _loginLogic.ValidateUserCredentials(credentials);
|
||||||
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
if (userData.Id != default)
|
||||||
if (existingUserConfig is not null)
|
|
||||||
{
|
{
|
||||||
//create hashes of the login credentials.
|
AuthCookie authCookie = new AuthCookie
|
||||||
var hashedUserName = Sha256_hash(credentials.UserName);
|
|
||||||
var hashedPassword = Sha256_hash(credentials.Password);
|
|
||||||
//compare against stored hash.
|
|
||||||
if (hashedUserName == existingUserConfig.UserNameHash &&
|
|
||||||
hashedPassword == existingUserConfig.UserPasswordHash)
|
|
||||||
{
|
{
|
||||||
//auth success, create auth cookie
|
UserData = userData,
|
||||||
//encrypt stuff.
|
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1)
|
||||||
AuthCookie authCookie = new AuthCookie
|
};
|
||||||
{
|
var serializedCookie = JsonSerializer.Serialize(authCookie);
|
||||||
Id = 1, //this is hardcoded for now
|
var encryptedCookie = _dataProtector.Protect(serializedCookie);
|
||||||
UserName = credentials.UserName,
|
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
|
||||||
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1)
|
return Json(true);
|
||||||
};
|
|
||||||
var serializedCookie = JsonSerializer.Serialize(authCookie);
|
|
||||||
var encryptedCookie = _dataProtector.Protect(serializedCookie);
|
|
||||||
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
|
|
||||||
return Json(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -68,26 +73,33 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return Json(false);
|
return Json(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Register(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.RegisterNewUser(credentials);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult RequestResetPassword(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.RequestResetPassword(credentials);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult PerformPasswordReset(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.ResetPasswordByUser(credentials);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
[Authorize] //User must already be logged in to do this.
|
[Authorize] //User must already be logged in to do this.
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult CreateLoginCreds(LoginModel credentials)
|
public IActionResult CreateLoginCreds(LoginModel credentials)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var configFileContents = System.IO.File.ReadAllText("userConfig.json");
|
var result = _loginLogic.CreateRootUserCredentials(credentials);
|
||||||
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
return Json(result);
|
||||||
if (existingUserConfig is not null)
|
|
||||||
{
|
|
||||||
//create hashes of the login credentials.
|
|
||||||
var hashedUserName = Sha256_hash(credentials.UserName);
|
|
||||||
var hashedPassword = Sha256_hash(credentials.Password);
|
|
||||||
//copy over settings that are off limits on the settings page.
|
|
||||||
existingUserConfig.EnableAuth = true;
|
|
||||||
existingUserConfig.UserNameHash = hashedUserName;
|
|
||||||
existingUserConfig.UserPasswordHash = hashedPassword;
|
|
||||||
}
|
|
||||||
System.IO.File.WriteAllText("userConfig.json", JsonSerializer.Serialize(existingUserConfig));
|
|
||||||
return Json(true);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -101,19 +113,13 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var configFileContents = System.IO.File.ReadAllText("userConfig.json");
|
var result = _loginLogic.DeleteRootUserCredentials();
|
||||||
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
|
||||||
if (existingUserConfig is not null)
|
|
||||||
{
|
|
||||||
//copy over settings that are off limits on the settings page.
|
|
||||||
existingUserConfig.EnableAuth = false;
|
|
||||||
existingUserConfig.UserNameHash = string.Empty;
|
|
||||||
existingUserConfig.UserPasswordHash = string.Empty;
|
|
||||||
}
|
|
||||||
System.IO.File.WriteAllText("userConfig.json", JsonSerializer.Serialize(existingUserConfig));
|
|
||||||
//destroy any login cookies.
|
//destroy any login cookies.
|
||||||
Response.Cookies.Delete("ACCESS_TOKEN");
|
if (result)
|
||||||
return Json(true);
|
{
|
||||||
|
Response.Cookies.Delete("ACCESS_TOKEN");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -128,20 +134,5 @@ namespace CarCareTracker.Controllers
|
|||||||
Response.Cookies.Delete("ACCESS_TOKEN");
|
Response.Cookies.Delete("ACCESS_TOKEN");
|
||||||
return Json(true);
|
return Json(true);
|
||||||
}
|
}
|
||||||
private static string Sha256_hash(string value)
|
|
||||||
{
|
|
||||||
StringBuilder Sb = new StringBuilder();
|
|
||||||
|
|
||||||
using (var hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
Encoding enc = Encoding.UTF8;
|
|
||||||
byte[] result = hash.ComputeHash(enc.GetBytes(value));
|
|
||||||
|
|
||||||
foreach (byte b in result)
|
|
||||||
Sb.Append(b.ToString("x2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||||
|
WORKDIR /App
|
||||||
|
|
||||||
|
COPY . ./
|
||||||
|
RUN dotnet restore
|
||||||
|
RUN dotnet publish -c Release -o out
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
WORKDIR /App
|
||||||
|
COPY --from=build-env /App/out .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["./CarCareTracker"]
|
||||||
11
Enum/ImportMode.cs
Normal file
11
Enum/ImportMode.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum ImportMode
|
||||||
|
{
|
||||||
|
ServiceRecord = 0,
|
||||||
|
RepairRecord = 1,
|
||||||
|
GasRecord = 2,
|
||||||
|
TaxRecord = 3,
|
||||||
|
UpgradeRecord = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Enum/ReminderMetric.cs
Normal file
9
Enum/ReminderMetric.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum ReminderMetric
|
||||||
|
{
|
||||||
|
Date = 0,
|
||||||
|
Odometer = 1,
|
||||||
|
Both = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Enum/ReminderUrgency.cs
Normal file
10
Enum/ReminderUrgency.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum ReminderUrgency
|
||||||
|
{
|
||||||
|
NotUrgent = 0,
|
||||||
|
Urgent = 1,
|
||||||
|
VeryUrgent = 2,
|
||||||
|
PastDue = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class CollisionRecordDataAccess : ICollisionRecordDataAccess
|
public class CollisionRecordDataAccess : ICollisionRecordDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "collisionrecords";
|
private static string tableName = "collisionrecords";
|
||||||
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
|
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class GasRecordDataAccess: IGasRecordDataAccess
|
public class GasRecordDataAccess: IGasRecordDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "gasrecords";
|
private static string tableName = "gasrecords";
|
||||||
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
|
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
|
|||||||
32
External/Implementations/NoteDataAccess.cs
vendored
32
External/Implementations/NoteDataAccess.cs
vendored
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,18 +7,26 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class NoteDataAccess: INoteDataAccess
|
public class NoteDataAccess: INoteDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "notes";
|
private static string tableName = "notes";
|
||||||
public Note GetNoteByVehicleId(int vehicleId)
|
public List<Note> GetNotesByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<Note>(tableName);
|
var table = db.GetCollection<Note>(tableName);
|
||||||
var noteToReturn = table.FindOne(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
var noteToReturn = table.Find(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
||||||
return noteToReturn ?? new Note();
|
return noteToReturn.ToList() ?? new List<Note>();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public bool SaveNoteToVehicleId(Note note)
|
public Note GetNoteById(int noteId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Note>(tableName);
|
||||||
|
return table.FindById(noteId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveNoteToVehicle(Note note)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
@@ -26,12 +35,21 @@ namespace CarCareTracker.External.Implementations
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public bool DeleteNoteByVehicleId(int vehicleId)
|
public bool DeleteNoteById(int noteId)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<Note>(tableName);
|
var table = db.GetCollection<Note>(tableName);
|
||||||
table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
table.Delete(noteId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteAllNotesByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Note>(tableName);
|
||||||
|
var notes = table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
57
External/Implementations/ReminderRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/ReminderRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class ReminderRecordDataAccess : IReminderRecordDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "reminderrecords";
|
||||||
|
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<ReminderRecord>(tableName);
|
||||||
|
var reminderRecords = table.Find(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
|
||||||
|
return reminderRecords.ToList() ?? new List<ReminderRecord>();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public ReminderRecord GetReminderRecordById(int reminderRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<ReminderRecord>(tableName);
|
||||||
|
return table.FindById(reminderRecordId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteReminderRecordById(int reminderRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<ReminderRecord>(tableName);
|
||||||
|
table.Delete(reminderRecordId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<ReminderRecord>(tableName);
|
||||||
|
table.Upsert(reminderRecord);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<ReminderRecord>(tableName);
|
||||||
|
var reminderRecords = table.DeleteMany(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class ServiceRecordDataAccess: IServiceRecordDataAccess
|
public class ServiceRecordDataAccess: IServiceRecordDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "servicerecords";
|
private static string tableName = "servicerecords";
|
||||||
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
|
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class TaxRecordDataAccess : ITaxRecordDataAccess
|
public class TaxRecordDataAccess : ITaxRecordDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "taxrecords";
|
private static string tableName = "taxrecords";
|
||||||
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
|
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
|
|||||||
57
External/Implementations/TokenRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/TokenRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class TokenRecordDataAccess : ITokenRecordDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "tokenrecords";
|
||||||
|
public List<Token> GetTokens()
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Token>(tableName);
|
||||||
|
return table.FindAll().ToList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Token GetTokenRecordByBody(string tokenBody)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Token>(tableName);
|
||||||
|
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.Body), tokenBody));
|
||||||
|
return tokenRecord ?? new Token();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public Token GetTokenRecordByEmailAddress(string emailAddress)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Token>(tableName);
|
||||||
|
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.EmailAddress), emailAddress));
|
||||||
|
return tokenRecord ?? new Token();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool CreateNewToken(Token token)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Token>(tableName);
|
||||||
|
table.Insert(token);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteToken(int tokenId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Token>(tableName);
|
||||||
|
table.Delete(tokenId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
External/Implementations/UpgradeRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/UpgradeRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class UpgradeRecordDataAccess : IUpgradeRecordDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "upgraderecords";
|
||||||
|
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
var upgradeRecords = table.Find(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
|
||||||
|
return upgradeRecords.ToList() ?? new List<UpgradeRecord>();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
return table.FindById(upgradeRecordId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
table.Delete(upgradeRecordId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
table.Upsert(upgradeRecord);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
var upgradeRecords = table.DeleteMany(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
External/Implementations/UserAccessDataAcces.cs
vendored
Normal file
88
External/Implementations/UserAccessDataAcces.cs
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class UserAccessDataAccess : IUserAccessDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "useraccessrecords";
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of vehicles user have access to.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<UserAccess> GetUserAccessByUserId(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
return table.Find(x=>x.Id.UserId == userId).ToList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
return table.Find(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId).FirstOrDefault();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public List<UserAccess> GetUserAccessByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
return table.Find(x => x.Id.VehicleId == vehicleId).ToList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveUserAccess(UserAccess userAccess)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
table.Upsert(userAccess);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteUserAccess(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
table.DeleteMany(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Delete all access records when a vehicle is deleted.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vehicleId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool DeleteAllAccessRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
table.DeleteMany(x=>x.Id.VehicleId == vehicleId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Delee all access records when a user is deleted.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool DeleteAllAccessRecordsByUserId(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserAccess>(tableName);
|
||||||
|
table.DeleteMany(x => x.Id.UserId == userId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
External/Implementations/UserConfigDataAccess.cs
vendored
Normal file
40
External/Implementations/UserConfigDataAccess.cs
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class UserConfigDataAccess: IUserConfigDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "userconfigrecords";
|
||||||
|
public UserConfigData GetUserConfig(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserConfigData>(tableName);
|
||||||
|
return table.FindById(userId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveUserConfig(UserConfigData userConfigData)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserConfigData>(tableName);
|
||||||
|
table.Upsert(userConfigData);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteUserConfig(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserConfigData>(tableName);
|
||||||
|
table.Delete(userId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
External/Implementations/UserRecordDataAccess.cs
vendored
Normal file
66
External/Implementations/UserRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class UserRecordDataAccess : IUserRecordDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "userrecords";
|
||||||
|
public List<UserData> GetUsers()
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
return table.FindAll().ToList();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UserData GetUserRecordByUserName(string userName)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
var userRecord = table.FindOne(Query.EQ(nameof(UserData.UserName), userName));
|
||||||
|
return userRecord ?? new UserData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UserData GetUserRecordByEmailAddress(string emailAddress)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
var userRecord = table.FindOne(Query.EQ(nameof(UserData.EmailAddress), emailAddress));
|
||||||
|
return userRecord ?? new UserData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UserData GetUserRecordById(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
var userRecord = table.FindById(userId);
|
||||||
|
return userRecord ?? new UserData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveUserRecord(UserData userRecord)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
table.Upsert(userRecord);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteUserRecord(int userId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UserData>(tableName);
|
||||||
|
table.Delete(userId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
using LiteDB;
|
||||||
|
|
||||||
@@ -6,14 +7,14 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
public class VehicleDataAccess: IVehicleDataAccess
|
public class VehicleDataAccess: IVehicleDataAccess
|
||||||
{
|
{
|
||||||
private static string dbName = "cartracker.db";
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "vehicles";
|
private static string tableName = "vehicles";
|
||||||
public bool SaveVehicle(Vehicle vehicle)
|
public bool SaveVehicle(Vehicle vehicle)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<Vehicle>(tableName);
|
var table = db.GetCollection<Vehicle>(tableName);
|
||||||
table.Upsert(vehicle);
|
var result = table.Upsert(vehicle);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
8
External/Interfaces/INoteDataAccess.cs
vendored
8
External/Interfaces/INoteDataAccess.cs
vendored
@@ -4,8 +4,10 @@ namespace CarCareTracker.External.Interfaces
|
|||||||
{
|
{
|
||||||
public interface INoteDataAccess
|
public interface INoteDataAccess
|
||||||
{
|
{
|
||||||
public Note GetNoteByVehicleId(int vehicleId);
|
public List<Note> GetNotesByVehicleId(int vehicleId);
|
||||||
public bool SaveNoteToVehicleId(Note note);
|
public Note GetNoteById(int noteId);
|
||||||
bool DeleteNoteByVehicleId(int vehicleId);
|
public bool SaveNoteToVehicle(Note note);
|
||||||
|
public bool DeleteNoteById(int noteId);
|
||||||
|
public bool DeleteAllNotesByVehicleId(int vehicleId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
External/Interfaces/IReminderRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/IReminderRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IReminderRecordDataAccess
|
||||||
|
{
|
||||||
|
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId);
|
||||||
|
public ReminderRecord GetReminderRecordById(int reminderRecordId);
|
||||||
|
public bool DeleteReminderRecordById(int reminderRecordId);
|
||||||
|
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord);
|
||||||
|
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
External/Interfaces/ITokenRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/ITokenRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface ITokenRecordDataAccess
|
||||||
|
{
|
||||||
|
public List<Token> GetTokens();
|
||||||
|
public Token GetTokenRecordByBody(string tokenBody);
|
||||||
|
public Token GetTokenRecordByEmailAddress(string emailAddress);
|
||||||
|
public bool CreateNewToken(Token token);
|
||||||
|
public bool DeleteToken(int tokenId);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
External/Interfaces/IUpgradeRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/IUpgradeRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUpgradeRecordDataAccess
|
||||||
|
{
|
||||||
|
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId);
|
||||||
|
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId);
|
||||||
|
public bool DeleteUpgradeRecordById(int upgradeRecordId);
|
||||||
|
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord);
|
||||||
|
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
External/Interfaces/IUserAccessDataAccess.cs
vendored
Normal file
15
External/Interfaces/IUserAccessDataAccess.cs
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUserAccessDataAccess
|
||||||
|
{
|
||||||
|
List<UserAccess> GetUserAccessByUserId(int userId);
|
||||||
|
UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId);
|
||||||
|
List<UserAccess> GetUserAccessByVehicleId(int vehicleId);
|
||||||
|
bool SaveUserAccess(UserAccess userAccess);
|
||||||
|
bool DeleteUserAccess(int userId, int vehicleId);
|
||||||
|
bool DeleteAllAccessRecordsByVehicleId(int vehicleId);
|
||||||
|
bool DeleteAllAccessRecordsByUserId(int userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
External/Interfaces/IUserConfigDataAccess.cs
vendored
Normal file
11
External/Interfaces/IUserConfigDataAccess.cs
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUserConfigDataAccess
|
||||||
|
{
|
||||||
|
public UserConfigData GetUserConfig(int userId);
|
||||||
|
public bool SaveUserConfig(UserConfigData userConfigData);
|
||||||
|
public bool DeleteUserConfig(int userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
External/Interfaces/IUserRecordDataAccess.cs
vendored
Normal file
14
External/Interfaces/IUserRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUserRecordDataAccess
|
||||||
|
{
|
||||||
|
public List<UserData> GetUsers();
|
||||||
|
public UserData GetUserRecordByUserName(string userName);
|
||||||
|
public UserData GetUserRecordByEmailAddress(string emailAddress);
|
||||||
|
public UserData GetUserRecordById(int userId);
|
||||||
|
public bool SaveUserRecord(UserData userRecord);
|
||||||
|
public bool DeleteUserRecord(int userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Filter/CollaboratorFilter.cs
Normal file
28
Filter/CollaboratorFilter.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Filter
|
||||||
|
{
|
||||||
|
public class CollaboratorFilter: ActionFilterAttribute
|
||||||
|
{
|
||||||
|
private readonly IUserLogic _userLogic;
|
||||||
|
public CollaboratorFilter(IUserLogic userLogic) {
|
||||||
|
_userLogic = userLogic;
|
||||||
|
}
|
||||||
|
public override void OnActionExecuting(ActionExecutingContext filterContext)
|
||||||
|
{
|
||||||
|
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
|
||||||
|
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
||||||
|
{
|
||||||
|
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
Helper/ConfigHelper.cs
Normal file
137
Helper/ConfigHelper.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IConfigHelper
|
||||||
|
{
|
||||||
|
UserConfig GetUserConfig(ClaimsPrincipal user);
|
||||||
|
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
||||||
|
public bool DeleteUserConfig(int userId);
|
||||||
|
}
|
||||||
|
public class ConfigHelper : IConfigHelper
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
private readonly IUserConfigDataAccess _userConfig;
|
||||||
|
private IMemoryCache _cache;
|
||||||
|
public ConfigHelper(IConfiguration serverConfig,
|
||||||
|
IUserConfigDataAccess userConfig,
|
||||||
|
IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
_config = serverConfig;
|
||||||
|
_userConfig = userConfig;
|
||||||
|
_cache = memoryCache;
|
||||||
|
}
|
||||||
|
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
|
||||||
|
{
|
||||||
|
var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
int userId = 0;
|
||||||
|
if (storedUserId != null)
|
||||||
|
{
|
||||||
|
userId = int.Parse(storedUserId);
|
||||||
|
}
|
||||||
|
bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)) || userId == -1;
|
||||||
|
if (isRootUser)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(StaticHelper.UserConfigPath))
|
||||||
|
{
|
||||||
|
//if file doesn't exist it might be because it's running on a mounted volume in docker.
|
||||||
|
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
||||||
|
}
|
||||||
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
|
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
||||||
|
if (existingUserConfig is not null)
|
||||||
|
{
|
||||||
|
//copy over settings that are off limits on the settings page.
|
||||||
|
configData.EnableAuth = existingUserConfig.EnableAuth;
|
||||||
|
configData.UserNameHash = existingUserConfig.UserNameHash;
|
||||||
|
configData.UserPasswordHash = existingUserConfig.UserPasswordHash;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
configData.EnableAuth = false;
|
||||||
|
configData.UserNameHash = string.Empty;
|
||||||
|
configData.UserPasswordHash = string.Empty;
|
||||||
|
}
|
||||||
|
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
||||||
|
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
var userConfig = new UserConfigData()
|
||||||
|
{
|
||||||
|
Id = userId,
|
||||||
|
UserConfig = configData
|
||||||
|
};
|
||||||
|
var result = _userConfig.SaveUserConfig(userConfig);
|
||||||
|
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool DeleteUserConfig(int userId)
|
||||||
|
{
|
||||||
|
_cache.Remove($"userConfig_{userId}");
|
||||||
|
var result = _userConfig.DeleteUserConfig(userId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public UserConfig GetUserConfig(ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
var serverConfig = new UserConfig
|
||||||
|
{
|
||||||
|
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
|
||||||
|
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
|
||||||
|
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
|
||||||
|
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
|
||||||
|
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||||
|
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||||
|
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)])
|
||||||
|
};
|
||||||
|
int userId = 0;
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
if (storedUserId != null)
|
||||||
|
{
|
||||||
|
userId = int.Parse(storedUserId);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return serverConfig;
|
||||||
|
}
|
||||||
|
return _cache.GetOrCreate<UserConfig>($"userConfig_{userId}", entry =>
|
||||||
|
{
|
||||||
|
entry.SlidingExpiration = TimeSpan.FromHours(1);
|
||||||
|
if (!user.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return serverConfig;
|
||||||
|
}
|
||||||
|
bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)) || userId == -1;
|
||||||
|
if (isRootUser)
|
||||||
|
{
|
||||||
|
return serverConfig;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = _userConfig.GetUserConfig(userId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return serverConfig;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return result.UserConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
public interface IFileHelper
|
public interface IFileHelper
|
||||||
{
|
{
|
||||||
string GetFullFilePath(string currentFilePath);
|
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
||||||
public string MoveFileFromTemp(string currentFilePath, string newFolder);
|
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||||
public bool DeleteFile(string currentFilePath);
|
bool DeleteFile(string currentFilePath);
|
||||||
}
|
}
|
||||||
public class FileHelper: IFileHelper
|
public class FileHelper: IFileHelper
|
||||||
{
|
{
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
_webEnv = webEnv;
|
_webEnv = webEnv;
|
||||||
}
|
}
|
||||||
public string GetFullFilePath(string currentFilePath)
|
public string GetFullFilePath(string currentFilePath, bool mustExist = true)
|
||||||
{
|
{
|
||||||
if (currentFilePath.StartsWith("/"))
|
if (currentFilePath.StartsWith("/"))
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,10 @@
|
|||||||
if (File.Exists(oldFilePath))
|
if (File.Exists(oldFilePath))
|
||||||
{
|
{
|
||||||
return oldFilePath;
|
return oldFilePath;
|
||||||
} else
|
} else if (!mustExist)
|
||||||
|
{
|
||||||
|
return oldFilePath;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
92
Helper/GasHelper.cs
Normal file
92
Helper/GasHelper.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IGasHelper
|
||||||
|
{
|
||||||
|
List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG);
|
||||||
|
}
|
||||||
|
public class GasHelper : IGasHelper
|
||||||
|
{
|
||||||
|
public List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG)
|
||||||
|
{
|
||||||
|
var computedResults = new List<GasRecordViewModel>();
|
||||||
|
int previousMileage = 0;
|
||||||
|
decimal unFactoredConsumption = 0.00M;
|
||||||
|
int unFactoredMileage = 0;
|
||||||
|
//perform computation.
|
||||||
|
for (int i = 0; i < result.Count; i++)
|
||||||
|
{
|
||||||
|
var currentObject = result[i];
|
||||||
|
decimal convertedConsumption;
|
||||||
|
if (useUKMPG && useMPG)
|
||||||
|
{
|
||||||
|
//if we're using UK MPG and the user wants imperial calculation insteace of l/100km
|
||||||
|
//if UK MPG is selected then the gas consumption are stored in liters but need to convert into UK gallons for computation.
|
||||||
|
convertedConsumption = currentObject.Gallons / 4.546M;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
convertedConsumption = currentObject.Gallons;
|
||||||
|
}
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
var deltaMileage = currentObject.Mileage - previousMileage;
|
||||||
|
var gasRecordViewModel = new GasRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = currentObject.Id,
|
||||||
|
VehicleId = currentObject.VehicleId,
|
||||||
|
MonthId = currentObject.Date.Month,
|
||||||
|
Date = currentObject.Date.ToShortDateString(),
|
||||||
|
Mileage = currentObject.Mileage,
|
||||||
|
Gallons = convertedConsumption,
|
||||||
|
Cost = currentObject.Cost,
|
||||||
|
DeltaMileage = deltaMileage,
|
||||||
|
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||||
|
};
|
||||||
|
if (currentObject.MissedFuelUp)
|
||||||
|
{
|
||||||
|
//if they missed a fuel up, we skip MPG calculation.
|
||||||
|
gasRecordViewModel.MilesPerGallon = 0;
|
||||||
|
//reset unFactored vars for missed fuel up because the numbers wont be reliable.
|
||||||
|
unFactoredConsumption = 0;
|
||||||
|
unFactoredMileage = 0;
|
||||||
|
}
|
||||||
|
else if (currentObject.IsFillToFull)
|
||||||
|
{
|
||||||
|
//if user filled to full.
|
||||||
|
gasRecordViewModel.MilesPerGallon = useMPG ? (unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
||||||
|
//reset unFactored vars
|
||||||
|
unFactoredConsumption = 0;
|
||||||
|
unFactoredMileage = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unFactoredConsumption += convertedConsumption;
|
||||||
|
unFactoredMileage += deltaMileage;
|
||||||
|
gasRecordViewModel.MilesPerGallon = 0;
|
||||||
|
}
|
||||||
|
computedResults.Add(gasRecordViewModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
computedResults.Add(new GasRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = currentObject.Id,
|
||||||
|
VehicleId = currentObject.VehicleId,
|
||||||
|
MonthId = currentObject.Date.Month,
|
||||||
|
Date = currentObject.Date.ToShortDateString(),
|
||||||
|
Mileage = currentObject.Mileage,
|
||||||
|
Gallons = convertedConsumption,
|
||||||
|
Cost = currentObject.Cost,
|
||||||
|
DeltaMileage = 0,
|
||||||
|
MilesPerGallon = 0,
|
||||||
|
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previousMileage = currentObject.Mileage;
|
||||||
|
}
|
||||||
|
return computedResults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Helper/MailHelper.cs
Normal file
85
Helper/MailHelper.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IMailHelper
|
||||||
|
{
|
||||||
|
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
||||||
|
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
||||||
|
}
|
||||||
|
public class MailHelper : IMailHelper
|
||||||
|
{
|
||||||
|
private readonly MailConfig mailConfig;
|
||||||
|
public MailHelper(
|
||||||
|
IConfiguration config
|
||||||
|
) {
|
||||||
|
//load mailConfig from Configuration
|
||||||
|
mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
|
||||||
|
}
|
||||||
|
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
|
||||||
|
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||||
|
}
|
||||||
|
string emailSubject = "Your Registration Token for LubeLogger";
|
||||||
|
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}";
|
||||||
|
var result = SendEmail(emailAddress, emailSubject, emailBody);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||||
|
}
|
||||||
|
string emailSubject = "Your Password Reset Token for LubeLogger";
|
||||||
|
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}";
|
||||||
|
var result = SendEmail(emailAddress, emailSubject, emailBody);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool SendEmail(string emailTo, string emailSubject, string emailBody) {
|
||||||
|
string to = emailTo;
|
||||||
|
string from = mailConfig.EmailFrom;
|
||||||
|
var server = mailConfig.EmailServer;
|
||||||
|
MailMessage message = new MailMessage(from, to);
|
||||||
|
message.Subject = emailSubject;
|
||||||
|
message.Body = emailBody;
|
||||||
|
SmtpClient client = new SmtpClient(server);
|
||||||
|
client.EnableSsl = mailConfig.UseSSL;
|
||||||
|
client.Port = mailConfig.Port;
|
||||||
|
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.Send(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
Helper/ReminderHelper.cs
Normal file
97
Helper/ReminderHelper.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IReminderHelper
|
||||||
|
{
|
||||||
|
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare);
|
||||||
|
}
|
||||||
|
public class ReminderHelper: IReminderHelper
|
||||||
|
{
|
||||||
|
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare)
|
||||||
|
{
|
||||||
|
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
||||||
|
foreach (var reminder in reminders)
|
||||||
|
{
|
||||||
|
var reminderViewModel = new ReminderRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = reminder.Id,
|
||||||
|
VehicleId = reminder.VehicleId,
|
||||||
|
Date = reminder.Date,
|
||||||
|
Mileage = reminder.Mileage,
|
||||||
|
Description = reminder.Description,
|
||||||
|
Notes = reminder.Notes,
|
||||||
|
Metric = reminder.Metric
|
||||||
|
};
|
||||||
|
if (reminder.Metric == ReminderMetric.Both)
|
||||||
|
{
|
||||||
|
if (reminder.Date < dateCompare)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < dateCompare.AddDays(7))
|
||||||
|
{
|
||||||
|
//if less than a week from today or less than 50 miles from current mileage then very urgent.
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
//have to specify by which metric this reminder is urgent.
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 50)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < dateCompare.AddDays(30))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 100)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reminder.Metric == ReminderMetric.Date)
|
||||||
|
{
|
||||||
|
if (reminder.Date < dateCompare)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < dateCompare.AddDays(7))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < dateCompare.AddDays(30))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reminder.Metric == ReminderMetric.Odometer)
|
||||||
|
{
|
||||||
|
if (reminder.Mileage < currentMileage)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 50)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 100)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reminderViewModels.Add(reminderViewModel);
|
||||||
|
}
|
||||||
|
return reminderViewModels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Helper/ReportHelper.cs
Normal file
82
Helper/ReportHelper.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IReportHelper
|
||||||
|
{
|
||||||
|
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0);
|
||||||
|
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0);
|
||||||
|
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0);
|
||||||
|
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0);
|
||||||
|
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0);
|
||||||
|
}
|
||||||
|
public class ReportHelper: IReportHelper
|
||||||
|
{
|
||||||
|
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
|
||||||
|
{
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
|
||||||
|
{
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
repairRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
|
||||||
|
{
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
|
||||||
|
{
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
|
||||||
|
{
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Helper/StaticHelper.cs
Normal file
27
Helper/StaticHelper.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// helper method for static vars
|
||||||
|
/// </summary>
|
||||||
|
public static class StaticHelper
|
||||||
|
{
|
||||||
|
public static string DbName = "data/cartracker.db";
|
||||||
|
public static string UserConfigPath = "config/userConfig.json";
|
||||||
|
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||||
|
|
||||||
|
public static string TruncateStrings(string input, int maxLength = 25)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
if (input.Length > maxLength)
|
||||||
|
{
|
||||||
|
return (input.Substring(0, maxLength) + "...");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
LICENSE
8
LICENSE
@@ -1,6 +1,12 @@
|
|||||||
|
LubeLogger by Hargata Softworks is licensed under the MIT License individual
|
||||||
|
personal use. Commercial users and/or corporate entities are required
|
||||||
|
to pay either a one time fee or maintain an active subscription in order to
|
||||||
|
continue using LubeLogger. For pricing information please contact us at
|
||||||
|
hargatasoftworks@gmail.com
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 ivancheahhh
|
Copyright (c) 2023 Hargata Softworks
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
354
Logic/LoginLogic.cs
Normal file
354
Logic/LoginLogic.cs
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Logic
|
||||||
|
{
|
||||||
|
public interface ILoginLogic
|
||||||
|
{
|
||||||
|
bool MakeUserAdmin(int userId, bool isAdmin);
|
||||||
|
OperationResponse GenerateUserToken(string emailAddress, bool autoNotify);
|
||||||
|
bool DeleteUserToken(int tokenId);
|
||||||
|
bool DeleteUser(int userId);
|
||||||
|
OperationResponse RegisterNewUser(LoginModel credentials);
|
||||||
|
OperationResponse RequestResetPassword(LoginModel credentials);
|
||||||
|
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
||||||
|
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||||
|
UserData ValidateUserCredentials(LoginModel credentials);
|
||||||
|
bool CheckIfUserIsValid(int userId);
|
||||||
|
bool CreateRootUserCredentials(LoginModel credentials);
|
||||||
|
bool DeleteRootUserCredentials();
|
||||||
|
List<UserData> GetAllUsers();
|
||||||
|
List<Token> GetAllTokens();
|
||||||
|
|
||||||
|
}
|
||||||
|
public class LoginLogic : ILoginLogic
|
||||||
|
{
|
||||||
|
private readonly IUserRecordDataAccess _userData;
|
||||||
|
private readonly ITokenRecordDataAccess _tokenData;
|
||||||
|
private readonly IMailHelper _mailHelper;
|
||||||
|
private IMemoryCache _cache;
|
||||||
|
public LoginLogic(IUserRecordDataAccess userData,
|
||||||
|
ITokenRecordDataAccess tokenData,
|
||||||
|
IMailHelper mailHelper,
|
||||||
|
IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
_userData = userData;
|
||||||
|
_tokenData = tokenData;
|
||||||
|
_mailHelper = mailHelper;
|
||||||
|
_cache = memoryCache;
|
||||||
|
}
|
||||||
|
public bool CheckIfUserIsValid(int userId)
|
||||||
|
{
|
||||||
|
if (userId == -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var result = _userData.GetUserRecordById(userId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return result.Id != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//handles user registration
|
||||||
|
public OperationResponse RegisterNewUser(LoginModel credentials)
|
||||||
|
{
|
||||||
|
//validate their token.
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||||
|
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||||
|
}
|
||||||
|
//token is valid, check if username and password is acceptable and that username is unique.
|
||||||
|
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" };
|
||||||
|
}
|
||||||
|
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||||
|
if (existingUser.Id != default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Username already taken" };
|
||||||
|
}
|
||||||
|
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||||
|
if (existingUserWithEmail.Id != default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
|
||||||
|
}
|
||||||
|
//username is unique then we delete the token and create the user.
|
||||||
|
_tokenData.DeleteToken(existingToken.Id);
|
||||||
|
var newUser = new UserData()
|
||||||
|
{
|
||||||
|
UserName = credentials.UserName,
|
||||||
|
Password = GetHash(credentials.Password),
|
||||||
|
EmailAddress = credentials.EmailAddress
|
||||||
|
};
|
||||||
|
var result = _userData.SaveUserRecord(newUser);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a token and notifies user via email so they can reset their password.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentials"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public OperationResponse RequestResetPassword(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||||
|
if (existingUser.Id != default)
|
||||||
|
{
|
||||||
|
//user exists, generate a token and send email.
|
||||||
|
//check to see if there is an existing token sent to the user.
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByEmailAddress(existingUser.EmailAddress);
|
||||||
|
if (existingToken.Id == default)
|
||||||
|
{
|
||||||
|
var token = new Token()
|
||||||
|
{
|
||||||
|
Body = NewToken(),
|
||||||
|
EmailAddress = existingUser.EmailAddress
|
||||||
|
};
|
||||||
|
var result = _tokenData.CreateNewToken(token);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
result = _mailHelper.NotifyUserForPasswordReset(existingUser.EmailAddress, token.Body).Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//for security purposes we want to always return true for this method.
|
||||||
|
//otherwise someone can spam the reset password method to sniff out users.
|
||||||
|
return new OperationResponse { Success = true, Message = "If your user exists in the system you should receive an email shortly with instructions on how to proceed." };
|
||||||
|
}
|
||||||
|
public OperationResponse ResetPasswordByUser(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||||
|
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(credentials.Password))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "New Password cannot be blank" };
|
||||||
|
}
|
||||||
|
//if token is valid.
|
||||||
|
var existingUser = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||||
|
if (existingUser.Id == default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Unable to locate user" };
|
||||||
|
}
|
||||||
|
existingUser.Password = GetHash(credentials.Password);
|
||||||
|
var result = _userData.SaveUserRecord(existingUser);
|
||||||
|
//delete token
|
||||||
|
_tokenData.DeleteToken(existingToken.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Password resetted, you will be redirected to login page shortly." };
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an empty user if can't auth against neither root nor db user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentials">credentials from login page</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public UserData ValidateUserCredentials(LoginModel credentials)
|
||||||
|
{
|
||||||
|
if (UserIsRoot(credentials))
|
||||||
|
{
|
||||||
|
return new UserData()
|
||||||
|
{
|
||||||
|
Id = -1,
|
||||||
|
UserName = credentials.UserName,
|
||||||
|
IsAdmin = true,
|
||||||
|
IsRootUser = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//authenticate via DB.
|
||||||
|
var result = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||||
|
if (GetHash(credentials.Password) == result.Password)
|
||||||
|
{
|
||||||
|
result.Password = string.Empty;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new UserData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#region "Admin Functions"
|
||||||
|
public bool MakeUserAdmin(int userId, bool isAdmin)
|
||||||
|
{
|
||||||
|
var user = _userData.GetUserRecordById(userId);
|
||||||
|
if (user == default)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
user.IsAdmin = isAdmin;
|
||||||
|
var result = _userData.SaveUserRecord(user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public List<UserData> GetAllUsers()
|
||||||
|
{
|
||||||
|
var result = _userData.GetUsers();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public List<Token> GetAllTokens()
|
||||||
|
{
|
||||||
|
var result = _tokenData.GetTokens();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public OperationResponse GenerateUserToken(string emailAddress, bool autoNotify)
|
||||||
|
{
|
||||||
|
//check if email address already has a token attached to it.
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
|
||||||
|
if (existingToken.Id != default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "There is an existing token tied to this email address" };
|
||||||
|
}
|
||||||
|
var token = new Token()
|
||||||
|
{
|
||||||
|
Body = NewToken(),
|
||||||
|
EmailAddress = emailAddress
|
||||||
|
};
|
||||||
|
var result = _tokenData.CreateNewToken(token);
|
||||||
|
if (result && autoNotify)
|
||||||
|
{
|
||||||
|
result = _mailHelper.NotifyUserForRegistration(emailAddress, token.Body).Success;
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Token Generated, but Email failed to send, please check your SMTP settings." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Token Generated!" };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool DeleteUserToken(int tokenId)
|
||||||
|
{
|
||||||
|
var result = _tokenData.DeleteToken(tokenId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public bool DeleteUser(int userId)
|
||||||
|
{
|
||||||
|
var result = _userData.DeleteUserRecord(userId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public OperationResponse ResetUserPassword(LoginModel credentials)
|
||||||
|
{
|
||||||
|
//user might have forgotten their password.
|
||||||
|
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||||
|
if (existingUser.Id == default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Unable to find user" };
|
||||||
|
}
|
||||||
|
var newPassword = Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
|
existingUser.Password = GetHash(newPassword);
|
||||||
|
var result = _userData.SaveUserRecord(existingUser);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = newPassword };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region "Root User"
|
||||||
|
public bool CreateRootUserCredentials(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
|
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
||||||
|
if (existingUserConfig is not null)
|
||||||
|
{
|
||||||
|
//create hashes of the login credentials.
|
||||||
|
var hashedUserName = GetHash(credentials.UserName);
|
||||||
|
var hashedPassword = GetHash(credentials.Password);
|
||||||
|
//copy over settings that are off limits on the settings page.
|
||||||
|
existingUserConfig.EnableAuth = true;
|
||||||
|
existingUserConfig.UserNameHash = hashedUserName;
|
||||||
|
existingUserConfig.UserPasswordHash = hashedPassword;
|
||||||
|
}
|
||||||
|
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig));
|
||||||
|
_cache.Remove("userConfig_-1");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public bool DeleteRootUserCredentials()
|
||||||
|
{
|
||||||
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
|
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
||||||
|
if (existingUserConfig is not null)
|
||||||
|
{
|
||||||
|
//copy over settings that are off limits on the settings page.
|
||||||
|
existingUserConfig.EnableAuth = false;
|
||||||
|
existingUserConfig.UserNameHash = string.Empty;
|
||||||
|
existingUserConfig.UserPasswordHash = string.Empty;
|
||||||
|
}
|
||||||
|
//clear out the cached config for the root user.
|
||||||
|
_cache.Remove("userConfig_-1");
|
||||||
|
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private bool UserIsRoot(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
|
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
||||||
|
if (existingUserConfig is not null)
|
||||||
|
{
|
||||||
|
//create hashes of the login credentials.
|
||||||
|
var hashedUserName = GetHash(credentials.UserName);
|
||||||
|
var hashedPassword = GetHash(credentials.Password);
|
||||||
|
//compare against stored hash.
|
||||||
|
if (hashedUserName == existingUserConfig.UserNameHash &&
|
||||||
|
hashedPassword == existingUserConfig.UserPasswordHash)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
private static string GetHash(string value)
|
||||||
|
{
|
||||||
|
StringBuilder Sb = new StringBuilder();
|
||||||
|
|
||||||
|
using (var hash = SHA256.Create())
|
||||||
|
{
|
||||||
|
Encoding enc = Encoding.UTF8;
|
||||||
|
byte[] result = hash.ComputeHash(enc.GetBytes(value));
|
||||||
|
|
||||||
|
foreach (byte b in result)
|
||||||
|
Sb.Append(b.ToString("x2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sb.ToString();
|
||||||
|
}
|
||||||
|
private string NewToken()
|
||||||
|
{
|
||||||
|
return Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Logic/UserLogic.cs
Normal file
118
Logic/UserLogic.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Logic
|
||||||
|
{
|
||||||
|
public interface IUserLogic
|
||||||
|
{
|
||||||
|
List<UserCollaborator> GetCollaboratorsForVehicle(int vehicleId);
|
||||||
|
bool AddUserAccessToVehicle(int userId, int vehicleId);
|
||||||
|
bool DeleteCollaboratorFromVehicle(int userId, int vehicleId);
|
||||||
|
OperationResponse AddCollaboratorToVehicle(int vehicleId, string username);
|
||||||
|
List<Vehicle> FilterUserVehicles(List<Vehicle> results, int userId);
|
||||||
|
bool UserCanEditVehicle(int userId, int vehicleId);
|
||||||
|
bool DeleteAllAccessToVehicle(int vehicleId);
|
||||||
|
bool DeleteAllAccessToUser(int userId);
|
||||||
|
}
|
||||||
|
public class UserLogic: IUserLogic
|
||||||
|
{
|
||||||
|
private readonly IUserAccessDataAccess _userAccess;
|
||||||
|
private readonly IUserRecordDataAccess _userData;
|
||||||
|
public UserLogic(IUserAccessDataAccess userAccess,
|
||||||
|
IUserRecordDataAccess userData) {
|
||||||
|
_userAccess = userAccess;
|
||||||
|
_userData = userData;
|
||||||
|
}
|
||||||
|
public List<UserCollaborator> GetCollaboratorsForVehicle(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _userAccess.GetUserAccessByVehicleId(vehicleId);
|
||||||
|
var convertedResult = new List<UserCollaborator>();
|
||||||
|
//convert useraccess to usercollaborator
|
||||||
|
foreach(UserAccess userAccess in result)
|
||||||
|
{
|
||||||
|
var userCollaborator = new UserCollaborator
|
||||||
|
{
|
||||||
|
UserName = _userData.GetUserRecordById(userAccess.Id.UserId).UserName,
|
||||||
|
UserVehicle = userAccess.Id
|
||||||
|
};
|
||||||
|
convertedResult.Add(userCollaborator);
|
||||||
|
}
|
||||||
|
return convertedResult;
|
||||||
|
}
|
||||||
|
public OperationResponse AddCollaboratorToVehicle(int vehicleId, string username)
|
||||||
|
{
|
||||||
|
//try to find existing user.
|
||||||
|
var existingUser = _userData.GetUserRecordByUserName(username);
|
||||||
|
if (existingUser.Id != default)
|
||||||
|
{
|
||||||
|
//user exists.
|
||||||
|
var result = AddUserAccessToVehicle(existingUser.Id, vehicleId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Collaborator Added" };
|
||||||
|
}
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" };
|
||||||
|
}
|
||||||
|
public bool DeleteCollaboratorFromVehicle(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _userAccess.DeleteUserAccess(userId, vehicleId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public bool AddUserAccessToVehicle(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
if (userId == -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var userVehicle = new UserVehicle { UserId = userId, VehicleId = vehicleId };
|
||||||
|
var userAccess = new UserAccess { Id = userVehicle };
|
||||||
|
var result = _userAccess.SaveUserAccess(userAccess);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public List<Vehicle> FilterUserVehicles(List<Vehicle> results, int userId)
|
||||||
|
{
|
||||||
|
//user is root user.
|
||||||
|
if (userId == -1)
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
var accessibleVehicles = _userAccess.GetUserAccessByUserId(userId);
|
||||||
|
if (accessibleVehicles.Any())
|
||||||
|
{
|
||||||
|
var vehicleIds = accessibleVehicles.Select(x => x.Id.VehicleId);
|
||||||
|
return results.Where(x => vehicleIds.Contains(x.Id)).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new List<Vehicle>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool UserCanEditVehicle(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
if (userId == -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId);
|
||||||
|
if (userAccess != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public bool DeleteAllAccessToVehicle(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _userAccess.DeleteAllAccessRecordsByVehicleId(vehicleId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public bool DeleteAllAccessToUser(int userId)
|
||||||
|
{
|
||||||
|
var result = _userAccess.DeleteAllAccessRecordsByUserId(userId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
MapProfile/FuellyMappers.cs
Normal file
22
MapProfile/FuellyMappers.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
|
||||||
|
namespace CarCareTracker.MapProfile
|
||||||
|
{
|
||||||
|
public class FuellyMapper: ClassMap<ImportModel>
|
||||||
|
{
|
||||||
|
public FuellyMapper()
|
||||||
|
{
|
||||||
|
Map(m => m.Date).Name(["date", "fuelup_date"]);
|
||||||
|
Map(m => m.Odometer).Name(["odometer"]);
|
||||||
|
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
|
||||||
|
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
|
||||||
|
Map(m => m.Notes).Name("notes", "note");
|
||||||
|
Map(m => m.Price).Name(["price"]);
|
||||||
|
Map(m => m.PartialFuelUp).Name(["partial_fuelup"]);
|
||||||
|
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
|
||||||
|
Map(m => m.Description).Name(["description"]);
|
||||||
|
Map(m => m.MissedFuelUp).Name(["missed_fuelup"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
@@ -14,17 +15,20 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
private IHttpContextAccessor _httpContext;
|
private IHttpContextAccessor _httpContext;
|
||||||
private IDataProtector _dataProtector;
|
private IDataProtector _dataProtector;
|
||||||
|
private ILoginLogic _loginLogic;
|
||||||
private bool enableAuth;
|
private bool enableAuth;
|
||||||
public Authen(
|
public Authen(
|
||||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
UrlEncoder encoder,
|
UrlEncoder encoder,
|
||||||
ILoggerFactory logger,
|
ILoggerFactory logger,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
ILoginLogic loginLogic,
|
||||||
IDataProtectionProvider securityProvider,
|
IDataProtectionProvider securityProvider,
|
||||||
IHttpContextAccessor httpContext) : base(options, logger, encoder)
|
IHttpContextAccessor httpContext) : base(options, logger, encoder)
|
||||||
{
|
{
|
||||||
_httpContext = httpContext;
|
_httpContext = httpContext;
|
||||||
_dataProtector = securityProvider.CreateProtector("login");
|
_dataProtector = securityProvider.CreateProtector("login");
|
||||||
|
_loginLogic = loginLogic;
|
||||||
enableAuth = bool.Parse(configuration["EnableAuth"]);
|
enableAuth = bool.Parse(configuration["EnableAuth"]);
|
||||||
}
|
}
|
||||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
@@ -35,7 +39,9 @@ namespace CarCareTracker.Middleware
|
|||||||
var appIdentity = new ClaimsIdentity("Custom");
|
var appIdentity = new ClaimsIdentity("Custom");
|
||||||
var userIdentity = new List<Claim>
|
var userIdentity = new List<Claim>
|
||||||
{
|
{
|
||||||
new(ClaimTypes.Name, "admin")
|
new(ClaimTypes.Name, "admin"),
|
||||||
|
new(ClaimTypes.NameIdentifier, "-1"),
|
||||||
|
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
|
||||||
};
|
};
|
||||||
appIdentity.AddClaims(userIdentity);
|
appIdentity.AddClaims(userIdentity);
|
||||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||||
@@ -45,45 +51,112 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
//auth is enabled by user, we will have to authenticate the user via a ticket retrieved from the auth cookie.
|
//auth is enabled by user, we will have to authenticate the user via a ticket retrieved from the auth cookie.
|
||||||
var access_token = _httpContext.HttpContext.Request.Cookies["ACCESS_TOKEN"];
|
var access_token = _httpContext.HttpContext.Request.Cookies["ACCESS_TOKEN"];
|
||||||
if (string.IsNullOrWhiteSpace(access_token))
|
//auth using Basic Auth for API.
|
||||||
|
var request_header = _httpContext.HttpContext.Request.Headers["Authorization"];
|
||||||
|
if (string.IsNullOrWhiteSpace(access_token) && string.IsNullOrWhiteSpace(request_header))
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Fail("Cookie is invalid or does not exist.");
|
return AuthenticateResult.Fail("Cookie is invalid or does not exist.");
|
||||||
}
|
}
|
||||||
else
|
else if (!string.IsNullOrWhiteSpace(request_header))
|
||||||
{
|
{
|
||||||
//decrypt the access token.
|
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||||
var decryptedCookie = _dataProtector.Unprotect(access_token);
|
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||||
AuthCookie authCookie = JsonSerializer.Deserialize<AuthCookie>(decryptedCookie);
|
string decodedString = System.Text.Encoding.UTF8.GetString(data);
|
||||||
if (authCookie != null)
|
var splitString = decodedString.Split(":");
|
||||||
|
if (splitString.Count() != 2)
|
||||||
{
|
{
|
||||||
//validate auth cookie
|
return AuthenticateResult.Fail("Invalid credentials");
|
||||||
if (authCookie.ExpiresOn < DateTime.Now)
|
}
|
||||||
{
|
else
|
||||||
//if cookie is expired
|
{
|
||||||
return AuthenticateResult.Fail("Expired credentials");
|
var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||||
}
|
if (userData.Id != default)
|
||||||
else if (authCookie.Id == default || string.IsNullOrWhiteSpace(authCookie.UserName))
|
|
||||||
{
|
|
||||||
return AuthenticateResult.Fail("Corrupted credentials");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var appIdentity = new ClaimsIdentity("Custom");
|
var appIdentity = new ClaimsIdentity("Custom");
|
||||||
var userIdentity = new List<Claim>
|
var userIdentity = new List<Claim>
|
||||||
{
|
{
|
||||||
new(ClaimTypes.Name, authCookie.UserName)
|
new(ClaimTypes.Name, splitString[0]),
|
||||||
|
new(ClaimTypes.NameIdentifier, userData.Id.ToString())
|
||||||
};
|
};
|
||||||
|
if (userData.IsAdmin)
|
||||||
|
{
|
||||||
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin)));
|
||||||
|
}
|
||||||
|
if (userData.IsRootUser)
|
||||||
|
{
|
||||||
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||||
|
}
|
||||||
appIdentity.AddClaims(userIdentity);
|
appIdentity.AddClaims(userIdentity);
|
||||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||||
return AuthenticateResult.Success(ticket);
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(access_token))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//decrypt the access token.
|
||||||
|
var decryptedCookie = _dataProtector.Unprotect(access_token);
|
||||||
|
AuthCookie authCookie = JsonSerializer.Deserialize<AuthCookie>(decryptedCookie);
|
||||||
|
if (authCookie != null)
|
||||||
|
{
|
||||||
|
//validate auth cookie
|
||||||
|
if (authCookie.ExpiresOn < DateTime.Now)
|
||||||
|
{
|
||||||
|
//if cookie is expired
|
||||||
|
return AuthenticateResult.Fail("Expired credentials");
|
||||||
|
}
|
||||||
|
else if (authCookie.UserData is null || authCookie.UserData.Id == default || string.IsNullOrWhiteSpace(authCookie.UserData.UserName))
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Corrupted credentials");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_loginLogic.CheckIfUserIsValid(authCookie.UserData.Id))
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Cookie points to non-existant user.");
|
||||||
|
}
|
||||||
|
//validate if user is still valid
|
||||||
|
var appIdentity = new ClaimsIdentity("Custom");
|
||||||
|
var userIdentity = new List<Claim>
|
||||||
|
{
|
||||||
|
new(ClaimTypes.Name, authCookie.UserData.UserName),
|
||||||
|
new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()),
|
||||||
|
new(ClaimTypes.Role, "CookieAuth")
|
||||||
|
};
|
||||||
|
if (authCookie.UserData.IsAdmin)
|
||||||
|
{
|
||||||
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin)));
|
||||||
|
}
|
||||||
|
if (authCookie.UserData.IsRootUser)
|
||||||
|
{
|
||||||
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||||
|
}
|
||||||
|
appIdentity.AddClaims(userIdentity);
|
||||||
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||||
|
return AuthenticateResult.Success(ticket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Corrupted credentials");
|
||||||
|
}
|
||||||
|
}
|
||||||
return AuthenticateResult.Fail("Invalid credentials");
|
return AuthenticateResult.Fail("Invalid credentials");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
||||||
{
|
{
|
||||||
|
if (Request.RouteValues.TryGetValue("controller", out object value))
|
||||||
|
{
|
||||||
|
if (value.ToString().ToLower() == "api")
|
||||||
|
{
|
||||||
|
Response.StatusCode = 401;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
Response.Redirect("/Login/Index");
|
Response.Redirect("/Login/Index");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
8
Models/Admin/AdminViewModel.cs
Normal file
8
Models/Admin/AdminViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class AdminViewModel
|
||||||
|
{
|
||||||
|
public List<UserData> Users { get; set; }
|
||||||
|
public List<Token> Tokens { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Configuration/MailConfig.cs
Normal file
12
Models/Configuration/MailConfig.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class MailConfig
|
||||||
|
{
|
||||||
|
public string EmailServer { get; set; }
|
||||||
|
public string EmailFrom { get; set; }
|
||||||
|
public bool UseSSL { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Gallons { get; set; }
|
public decimal Gallons { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
|
public bool IsFillToFull { get; set; } = true;
|
||||||
|
public bool MissedFuelUp { get; set; } = false;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,19 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Gallons { get; set; }
|
public decimal Gallons { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
|
public bool IsFillToFull { get; set; } = true;
|
||||||
|
public bool MissedFuelUp { get; set; } = false;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public GasRecord ToGasRecord() { return new GasRecord { Id = Id, Cost = Cost, Date = DateTime.Parse(Date), Gallons = Gallons, Mileage = Mileage, VehicleId = VehicleId, Files = Files }; }
|
public GasRecord ToGasRecord() { return new GasRecord {
|
||||||
|
Id = Id,
|
||||||
|
Cost = Cost,
|
||||||
|
Date = DateTime.Parse(Date),
|
||||||
|
Gallons = Gallons,
|
||||||
|
Mileage = Mileage,
|
||||||
|
VehicleId = VehicleId,
|
||||||
|
Files = Files,
|
||||||
|
IsFillToFull = IsFillToFull,
|
||||||
|
MissedFuelUp = MissedFuelUp
|
||||||
|
}; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
Models/GasRecord/GasRecordInputContainer.cs
Normal file
8
Models/GasRecord/GasRecordInputContainer.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class GasRecordInputContainer
|
||||||
|
{
|
||||||
|
public bool UseKwh { get; set; }
|
||||||
|
public GasRecordInput GasRecord { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
|
public int MonthId { get; set; }
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// American moment
|
/// American moment
|
||||||
|
|||||||
8
Models/GasRecord/GasRecordViewModelContainer.cs
Normal file
8
Models/GasRecord/GasRecordViewModelContainer.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class GasRecordViewModelContainer
|
||||||
|
{
|
||||||
|
public bool UseKwh { get; set; }
|
||||||
|
public List<GasRecordViewModel> GasRecords { get; set; } = new List<GasRecordViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Models/ImportModel.cs
Normal file
51
Models/ImportModel.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Import model used for importing records via CSV.
|
||||||
|
/// </summary>
|
||||||
|
public class ImportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public string FuelConsumed { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
public string Price { get; set; }
|
||||||
|
public string PartialFuelUp { get; set; }
|
||||||
|
public string IsFillToFull { get; set; }
|
||||||
|
public string MissedFuelUp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServiceRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
}
|
||||||
|
public class TaxRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
}
|
||||||
|
public class GasRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string FuelConsumed { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
public string FuelEconomy { get; set; }
|
||||||
|
}
|
||||||
|
public class ReminderExportModel
|
||||||
|
{
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Urgency { get; set; }
|
||||||
|
public string Metric { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
namespace CarCareTracker.Models
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Import model used for importing Gas records.
|
|
||||||
/// </summary>
|
|
||||||
public class GasRecordImport
|
|
||||||
{
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
public int Odometer { get; set; }
|
|
||||||
public decimal FuelConsumed { get; set; }
|
|
||||||
public decimal Cost { get; set; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Import model used for importing Service and Repair records.
|
|
||||||
/// </summary>
|
|
||||||
public class ServiceRecordImport
|
|
||||||
{
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
public int Odometer { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Notes { get; set; }
|
|
||||||
public decimal Cost { get; set; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Import model used for importing tax records.
|
|
||||||
/// </summary>
|
|
||||||
public class TaxRecordImport
|
|
||||||
{
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Notes { get; set; }
|
|
||||||
public decimal Cost { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class AuthCookie
|
public class AuthCookie
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public UserData UserData { get; set; }
|
||||||
public string UserName { get; set; }
|
|
||||||
public DateTime ExpiresOn { get; set; }
|
public DateTime ExpiresOn { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
{
|
{
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
public string Token { get; set; }
|
||||||
public bool IsPersistent { get; set; } = false;
|
public bool IsPersistent { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
Models/Login/Token.cs
Normal file
9
Models/Login/Token.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class Token
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
public string NoteText { get; set; }
|
public string NoteText { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
Models/OperationResponse.cs
Normal file
8
Models/OperationResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class OperationResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Models/Reminder/ReminderRecord.cs
Normal file
13
Models/Reminder/ReminderRecord.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReminderRecord
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Models/Reminder/ReminderRecordInput.cs
Normal file
21
Models/Reminder/ReminderRecordInput.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReminderRecordInput
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public string Date { get; set; } = DateTime.Now.AddDays(1).ToShortDateString();
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||||
|
public ReminderRecord ToReminderRecord() { return new ReminderRecord {
|
||||||
|
Id = Id,
|
||||||
|
VehicleId = VehicleId,
|
||||||
|
Date = DateTime.Parse(string.IsNullOrWhiteSpace(Date) ? DateTime.Now.AddDays(1).ToShortDateString() : Date),
|
||||||
|
Mileage = Mileage,
|
||||||
|
Description = Description,
|
||||||
|
Metric = Metric,
|
||||||
|
Notes = Notes }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Models/Reminder/ReminderRecordViewModel.cs
Normal file
17
Models/Reminder/ReminderRecordViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReminderRecordViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Reason why this reminder is urgent
|
||||||
|
/// </summary>
|
||||||
|
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||||
|
public ReminderUrgency Urgency { get; set; } = ReminderUrgency.NotUrgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace CarCareTracker.Models
|
namespace CarCareTracker.Models
|
||||||
{
|
{
|
||||||
public class GasCostForVehicleByMonth
|
public class CostForVehicleByMonth
|
||||||
{
|
{
|
||||||
|
public int MonthId { get; set; }
|
||||||
public string MonthName { get; set; }
|
public string MonthName { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
public decimal GasRecordSum { get; set; }
|
public decimal GasRecordSum { get; set; }
|
||||||
public decimal TaxRecordSum { get; set; }
|
public decimal TaxRecordSum { get; set; }
|
||||||
public decimal CollisionRecordSum { get; set; }
|
public decimal CollisionRecordSum { get; set; }
|
||||||
|
public decimal UpgradeRecordSum { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
Models/Report/GenericReportModel.cs
Normal file
15
Models/Report/GenericReportModel.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic Model used for vehicle history report.
|
||||||
|
/// </summary>
|
||||||
|
public class GenericReportModel
|
||||||
|
{
|
||||||
|
public ImportMode DataType { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Models/Report/ReminderMakeUpForVehicle.cs
Normal file
10
Models/Report/ReminderMakeUpForVehicle.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReminderMakeUpForVehicle
|
||||||
|
{
|
||||||
|
public int NotUrgentCount { get; set; }
|
||||||
|
public int UrgentCount { get; set; }
|
||||||
|
public int VeryUrgentCount { get; set; }
|
||||||
|
public int PastDueCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Report/ReportViewModel.cs
Normal file
12
Models/Report/ReportViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReportViewModel
|
||||||
|
{
|
||||||
|
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
|
public List<CostForVehicleByMonth> FuelMileageForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
|
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
|
||||||
|
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
||||||
|
public List<int> Years { get; set; } = new List<int>();
|
||||||
|
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Report/VehicleHistoryViewModel.cs
Normal file
12
Models/Report/VehicleHistoryViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class VehicleHistoryViewModel
|
||||||
|
{
|
||||||
|
public Vehicle VehicleData { get; set; }
|
||||||
|
public List<GenericReportModel> VehicleHistory { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public decimal MPG { get; set; }
|
||||||
|
public decimal TotalCost { get; set; }
|
||||||
|
public decimal TotalGasCost { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Models/Upgrades/UpgradeRecord.cs
Normal file
14
Models/Upgrades/UpgradeRecord.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UpgradeRecord
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Models/Upgrades/UpgradeReportInput.cs
Normal file
15
Models/Upgrades/UpgradeReportInput.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UpgradeRecordInput
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public string Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/User/UserAccess.cs
Normal file
12
Models/User/UserAccess.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UserVehicle
|
||||||
|
{
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
}
|
||||||
|
public class UserAccess
|
||||||
|
{
|
||||||
|
public UserVehicle Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Models/User/UserCollaborator.cs
Normal file
8
Models/User/UserCollaborator.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UserCollaborator
|
||||||
|
{
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public UserVehicle UserVehicle { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Models/User/UserConfigData.cs
Normal file
11
Models/User/UserConfigData.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UserConfigData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// User ID
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
public UserConfig UserConfig { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/User/UserData.cs
Normal file
12
Models/User/UserData.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UserData
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public bool IsAdmin { get; set; }
|
||||||
|
public bool IsRootUser { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
public bool UseMPG { get; set; }
|
public bool UseMPG { get; set; }
|
||||||
public bool UseDescending { get; set; }
|
public bool UseDescending { get; set; }
|
||||||
public bool EnableAuth { get; set; }
|
public bool EnableAuth { get; set; }
|
||||||
|
public bool HideZero { get; set; }
|
||||||
|
public bool UseUKMPG {get;set;}
|
||||||
public string UserNameHash { get; set; }
|
public string UserNameHash { get; set; }
|
||||||
public string UserPasswordHash { get; set;}
|
public string UserPasswordHash { get; set;}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,6 @@
|
|||||||
public string Make { get; set; }
|
public string Make { get; set; }
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
public string LicensePlate { get; set; }
|
public string LicensePlate { get; set; }
|
||||||
|
public bool IsElectric { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
Program.cs
25
Program.cs
@@ -1,6 +1,7 @@
|
|||||||
using CarCareTracker.External.Implementations;
|
using CarCareTracker.External.Implementations;
|
||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
using CarCareTracker.Middleware;
|
using CarCareTracker.Middleware;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -15,10 +16,32 @@ builder.Services.AddSingleton<IServiceRecordDataAccess, ServiceRecordDataAccess>
|
|||||||
builder.Services.AddSingleton<IGasRecordDataAccess, GasRecordDataAccess>();
|
builder.Services.AddSingleton<IGasRecordDataAccess, GasRecordDataAccess>();
|
||||||
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
||||||
builder.Services.AddSingleton<ITaxRecordDataAccess, TaxRecordDataAccess>();
|
builder.Services.AddSingleton<ITaxRecordDataAccess, TaxRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IReminderRecordDataAccess, ReminderRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IUpgradeRecordDataAccess, UpgradeRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IUserRecordDataAccess, UserRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IUserConfigDataAccess, UserConfigDataAccess>();
|
||||||
|
|
||||||
|
//configure helpers
|
||||||
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
||||||
|
builder.Services.AddSingleton<IGasHelper, GasHelper>();
|
||||||
|
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
||||||
|
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
|
||||||
|
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
||||||
|
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
|
||||||
|
|
||||||
|
//configure logic
|
||||||
|
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
||||||
|
builder.Services.AddSingleton<IUserLogic, UserLogic>();
|
||||||
|
|
||||||
|
if (!Directory.Exists("data"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data");
|
||||||
|
}
|
||||||
|
|
||||||
//Additional JsonFile
|
//Additional JsonFile
|
||||||
builder.Configuration.AddJsonFile("userConfig.json", optional: true, reloadOnChange: true);
|
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
//Configure Auth
|
//Configure Auth
|
||||||
builder.Services.AddDataProtection();
|
builder.Services.AddDataProtection();
|
||||||
|
|||||||
54
README.md
54
README.md
@@ -11,4 +11,56 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
|||||||
- Bootstrap-DatePicker
|
- Bootstrap-DatePicker
|
||||||
- SweetAlert2
|
- SweetAlert2
|
||||||
- CsvHelper
|
- CsvHelper
|
||||||
- Chart.js
|
- Chart.js
|
||||||
|
|
||||||
|
## Docker Setup (GHCR)
|
||||||
|
1. Install Docker
|
||||||
|
2. Run `docker pull ghcr.io/hargata/lubelogger:latest`
|
||||||
|
3. CHECK culture in .env file, default is en_US, this will change the currency and date formats. You can also setup SMTP Config here.
|
||||||
|
4. If not using traefik, use docker-compose-notraefik.yml
|
||||||
|
5. Run `docker-compose up`
|
||||||
|
|
||||||
|
## Docker Setup (Manual Build)
|
||||||
|
1. Install Docker
|
||||||
|
2. Clone this repo
|
||||||
|
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
||||||
|
4. Run `docker build -t lubelogger -f Dockerfile .`
|
||||||
|
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
|
||||||
|
6. If not using traefik, use docker-compose-notraefik.yml
|
||||||
|
7. Run `docker-compose up`
|
||||||
|
|
||||||
|
## Additional Docker Instructions
|
||||||
|
|
||||||
|
### manual
|
||||||
|
|
||||||
|
- build
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t hargata/lubelog:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
- run
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d hargata/lubelog:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
add `-v` for persistent volumes as needed. Have a look at the docker-compose.yml for examples.
|
||||||
|
|
||||||
|
## docker-compose
|
||||||
|
|
||||||
|
- build image
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
- run
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
# or variant with traefik labels:
|
||||||
|
|
||||||
|
docker compose -f docker-compose.traefik.yml up
|
||||||
|
```
|
||||||
|
|||||||
127
Views/API/Index.cshtml
Normal file
127
Views/API/Index.cshtml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 mt-2">API</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<p class="lead">If authentication is enabled, use the credentials of the user for Basic Auth(RFC2617)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
<h6>Method</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<h6>Endpoint</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<h6>Description</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<h6>Parameters</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicles</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of vehicles
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
No Params
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/servicerecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of service records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/repairrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of repair records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/upgraderecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of upgrade records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/taxrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of tax records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/gasrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of gas records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
<br />
|
||||||
|
useMPG(bool) - Use Imperial Units and Calculation
|
||||||
|
<br />
|
||||||
|
useUKMPG(bool) - Use UK Imperial Calculation
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/reminders</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of reminders for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
153
Views/Admin/Index.cshtml
Normal file
153
Views/Admin/Index.cshtml
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Admin";
|
||||||
|
}
|
||||||
|
@inject IConfiguration config;
|
||||||
|
@{
|
||||||
|
bool emailServerIsSetup = true;
|
||||||
|
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
|
||||||
|
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
emailServerIsSetup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@model AdminViewModel
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-11">
|
||||||
|
<span class="display-6">Admin Panel</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5 col-12">
|
||||||
|
<span class="lead">Tokens</span>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Generate User Token</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 d-flex align-items-center">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
|
||||||
|
<label class="form-check-label" for="enableAutoNotify">Auto Notify(via Email)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-4">Token</th>
|
||||||
|
<th scope="col" class="col-6">Issued To</th>
|
||||||
|
<th scope="col" class="col-2">Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (Token token in Model.Tokens)
|
||||||
|
{
|
||||||
|
<tr class="d-flex">
|
||||||
|
<td class="col-4" style="cursor:pointer;" onclick="copyToClipboard(this)">@token.Body</td>
|
||||||
|
<td class="col-6 text-truncate">@token.EmailAddress</td>
|
||||||
|
<td class="col-2">
|
||||||
|
<button type="button" class="btn btn-danger" onclick="deleteToken(@token.Id, this)"><i class="bi bi-trash"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-7">
|
||||||
|
<span class="lead">Users</span>
|
||||||
|
<hr />
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-4">Username</th>
|
||||||
|
<th scope="col" class="col-4">Email</th>
|
||||||
|
<th scope="col" class="col-2">Is Admin</th>
|
||||||
|
<th scope="col" class="col-2">Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (UserData userData in Model.Users)
|
||||||
|
{
|
||||||
|
<tr class="d-flex" style="cursor:pointer;">
|
||||||
|
<td class="col-4">@userData.UserName</td>
|
||||||
|
<td class="col-4">@userData.EmailAddress</td>
|
||||||
|
<td class="col-2"><input class="form-check-input" type="checkbox" value="" onchange="updateUserAdmin(@userData.Id, this)" @(userData.IsAdmin ? "checked" : "")/></td>
|
||||||
|
<td class="col-2"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function updateUserAdmin(userId, sender){
|
||||||
|
var isChecked = $(sender).is(":checked");
|
||||||
|
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
|
||||||
|
if (data){
|
||||||
|
reloadPage();
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function reloadPage() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
function deleteToken(tokenId) {
|
||||||
|
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
reloadPage();
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function deleteUser(userId) {
|
||||||
|
$.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
reloadPage();
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function copyToClipboard(e) {
|
||||||
|
var textToCopy = e.textContent;
|
||||||
|
navigator.clipboard.writeText(textToCopy);
|
||||||
|
successToast("Copied to Clipboard");
|
||||||
|
}
|
||||||
|
function generateNewToken() {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Generate Token',
|
||||||
|
html: `
|
||||||
|
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address">
|
||||||
|
`,
|
||||||
|
confirmButtonText: 'Generate',
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const emailAddress = $("#inputEmail").val();
|
||||||
|
if (!emailAddress) {
|
||||||
|
Swal.showValidationMessage(`Please enter an email address`)
|
||||||
|
}
|
||||||
|
return { emailAddress }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
var autoNotify = $("#enableAutoNotify").is(":checked");
|
||||||
|
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
|
||||||
|
if (data.success) {
|
||||||
|
reloadPage();
|
||||||
|
} else {
|
||||||
|
errorToast(data.message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
@inject IConfiguration Configuration
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
@{
|
@{
|
||||||
var enableAuth = bool.Parse(Configuration[nameof(UserConfig.EnableAuth)]);
|
var enableAuth = config.GetUserConfig(User).EnableAuth;
|
||||||
}
|
}
|
||||||
@model string
|
@model string
|
||||||
@{
|
@{
|
||||||
@@ -9,24 +10,60 @@
|
|||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/garage.js" asp-append-version="true"></script>
|
<script src="~/js/garage.js" asp-append-version="true"></script>
|
||||||
}
|
}
|
||||||
|
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||||
|
<ul class="navbar-nav" id="homeTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-car-front me-2"></i>Garage</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>Settings</span></button>
|
||||||
|
</li>
|
||||||
|
@if (User.IsInRole("CookieAuth"))
|
||||||
|
{
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||||
|
{
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>Admin Panel</span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>Logout</span></button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex lubelogger-navbar">
|
||||||
<img src="/defaults/lubelogger_logo.png"/>
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
<div class="lubelogger-navbar-button">
|
||||||
|
<button type="button" class="btn btn-dark" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<ul class="nav nav-tabs" id="homeTab" role="tablist">
|
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link @(Model == "garage" ? "active" : "")" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>Garage</button>
|
<button class="nav-link @(Model == "garage" ? "active" : "")" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>Garage</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ms-auto" role="presentation">
|
<li class="nav-item ms-auto" role="presentation">
|
||||||
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear me-2"></i>Settings</button>
|
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear me-2"></i>Settings</button>
|
||||||
</li>
|
</li>
|
||||||
@if (enableAuth)
|
@if (User.IsInRole("CookieAuth"))
|
||||||
{
|
{
|
||||||
<li class="nav-item">
|
<li class="nav-item dropdown" role="presentation">
|
||||||
<button class="nav-link" onclick="performLogOut()">Logout</button>
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person me-2"></i>@User.Identity.Name</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>Admin Panel</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>Logout</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -38,7 +75,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,11 +82,10 @@
|
|||||||
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content" id="addVehicleModalContent">
|
<div class="modal-content" id="addVehicleModalContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
loadGarage();
|
loadGarage();
|
||||||
loadSettings();
|
bindWindowResize();
|
||||||
</script>
|
</script>
|
||||||
@@ -16,7 +16,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UseMPG">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UseMPG">
|
||||||
<label class="form-check-label" for="useMPG">Use Imperial Units for Fuel Economy Calculations(Miles, Gallons)</label>
|
<label class="form-check-label" for="useMPG">Use Imperial Calculation for Fuel Economy Calculations(MPG)<br /><small class="text-body-secondary">This Will Also Change Units to Miles and Gallons</small></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UseUKMPG">
|
||||||
|
<label class="form-check-label" for="useUKMPG">Use UK MPG Calculation<br /><small class="text-body-secondary">Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation</small></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
@@ -25,9 +29,16 @@
|
|||||||
<label class="form-check-label" for="useDescending">Sort lists in Descending Order(Newest to Oldest)</label>
|
<label class="form-check-label" for="useDescending">Sort lists in Descending Order(Newest to Oldest)</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onChange="enableAuthCheckChanged()" type="checkbox" role="switch" id="enableAuth" checked="@Model.EnableAuth">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.HideZero">
|
||||||
<label class="form-check-label" for="enableAuth">Enable Authentication</label>
|
<label class="form-check-label" for="hideZero">Replace @(0.ToString("C")) Costs with ---</label>
|
||||||
</div>
|
</div>
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" onChange="enableAuthCheckChanged()" type="checkbox" role="switch" id="enableAuth" checked="@Model.EnableAuth">
|
||||||
|
<label class="form-check-label" for="enableAuth">Enable Authentication</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -76,7 +87,9 @@
|
|||||||
useDarkMode: $("#enableDarkMode").is(':checked'),
|
useDarkMode: $("#enableDarkMode").is(':checked'),
|
||||||
enableCsvImports: $("#enableCsvImports").is(':checked'),
|
enableCsvImports: $("#enableCsvImports").is(':checked'),
|
||||||
useMPG: $("#useMPG").is(':checked'),
|
useMPG: $("#useMPG").is(':checked'),
|
||||||
useDescending: $("#useDescending").is(':checked')
|
useDescending: $("#useDescending").is(':checked'),
|
||||||
|
hideZero: $("#hideZero").is(":checked"),
|
||||||
|
useUKMpg: $("#useUKMPG").is(":checked")
|
||||||
}
|
}
|
||||||
$.post('/Home/WriteToSettings', { userConfig: userConfigObject}, function(data){
|
$.post('/Home/WriteToSettings', { userConfig: userConfigObject}, function(data){
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -110,7 +123,7 @@
|
|||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
$.post('/Login/CreateLoginCreds', { userName: result.value.username, password: result.value.password }, function (data) {
|
$.post('/Login/CreateLoginCreds', { userName: result.value.username, password: result.value.password }, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
window.location.href = '/Login';
|
setTimeout(function () { window.location.href = '/Login' }, 500);
|
||||||
} else {
|
} else {
|
||||||
errorToast("An error occurred, please try again later.");
|
errorToast("An error occurred, please try again later.");
|
||||||
}
|
}
|
||||||
|
|||||||
26
Views/Login/ForgotPassword.cshtml
Normal file
26
Views/Login/ForgotPassword.cshtml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "LubeLogger - Login";
|
||||||
|
}
|
||||||
|
@section Scripts {
|
||||||
|
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||||
|
}
|
||||||
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserName">Username</label>
|
||||||
|
<input type="text" id="inputUserName" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Request</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/ResetPassword" class="btn btn-link mt-2">I Have a Token</a>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||||
}
|
}
|
||||||
<div class="container chartContainer d-flex align-items-center justify-content-center">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<img src="/defaults/lubelogger_logo.png" />
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
@@ -14,14 +14,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserPassword">Password</label>
|
<label for="inputUserPassword">Password</label>
|
||||||
<input type="password" id="inputUserPassword" class="form-control">
|
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="inputPersistent">
|
<input class="form-check-input" type="checkbox" role="switch" id="inputPersistent">
|
||||||
<label class="form-check-label" for="inputPersistent">Remember Me</label>
|
<label class="form-check-label" for="inputPersistent">Remember Me</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()">Login</button>
|
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>Login</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">Forgot Password</a>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/Registration" class="btn btn-link mt-2">Register</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
35
Views/Login/Registration.cshtml
Normal file
35
Views/Login/Registration.cshtml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "LubeLogger - Register";
|
||||||
|
}
|
||||||
|
@section Scripts {
|
||||||
|
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||||
|
}
|
||||||
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputToken">Token</label>
|
||||||
|
<input type="text" id="inputToken" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserName">Email Address</label>
|
||||||
|
<input type="text" id="inputEmail" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserName">Username</label>
|
||||||
|
<input type="text" id="inputUserName" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserPassword">Password</label>
|
||||||
|
<input type="password" id="inputUserPassword" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="button" class="btn btn-warning mt-2" onclick="performRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>Register</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
31
Views/Login/ResetPassword.cshtml
Normal file
31
Views/Login/ResetPassword.cshtml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "LubeLogger - Register";
|
||||||
|
}
|
||||||
|
@section Scripts {
|
||||||
|
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||||
|
}
|
||||||
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputToken">Token</label>
|
||||||
|
<input type="text" id="inputToken" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserName">Email Address</label>
|
||||||
|
<input type="text" id="inputEmail" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputUserPassword">New Password</label>
|
||||||
|
<input type="password" id="inputUserPassword" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="button" class="btn btn-warning mt-2" onclick="performPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Reset Password</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
Views/Shared/401.cshtml
Normal file
1
Views/Shared/401.cshtml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<h1>Access Denied</h1>
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
<!DOCTYPE html>
|
@using CarCareTracker.Helper
|
||||||
@inject IConfiguration Configuration
|
<!DOCTYPE html>
|
||||||
|
@inject IConfigHelper config
|
||||||
@{
|
@{
|
||||||
var useDarkMode = bool.Parse(Configuration["UseDarkMode"]);
|
var userConfig = config.GetUserConfig(User);
|
||||||
var enableCsvImports = bool.Parse(Configuration["EnableCsvImports"]);
|
var useDarkMode = userConfig.UseDarkMode;
|
||||||
|
var enableCsvImports = userConfig.EnableCsvImports;
|
||||||
|
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||||
|
shortDatePattern = shortDatePattern.ToLower();
|
||||||
|
if (!shortDatePattern.Contains("dd"))
|
||||||
|
{
|
||||||
|
shortDatePattern = shortDatePattern.Replace("d", "dd");
|
||||||
|
}
|
||||||
|
if (!shortDatePattern.Contains("mm"))
|
||||||
|
{
|
||||||
|
shortDatePattern = shortDatePattern.Replace("m", "mm");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<html lang="en" data-bs-theme="@(useDarkMode ? "dark" : "light")">
|
<html lang="en" data-bs-theme="@(useDarkMode ? "dark" : "light")">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="LubeLogger" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<title>@ViewData["Title"] - CarCareTracker</title>
|
<title>@ViewData["Title"] - CarCareTracker</title>
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
||||||
@@ -15,6 +30,12 @@
|
|||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/css/loader.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/loader.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/sweetalert/sweetalert2.min.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/sweetalert/sweetalert2.min.css" asp-append-version="true" />
|
||||||
|
<link rel="icon" sizes="192x192" href="~/defaults/lubelogger_icon_192.png" />
|
||||||
|
<link rel="icon" sizes="128x128" href="~/defaults/lubelogger_icon_128.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="128x128" href="~/defaults/lubelogger_icon_128.png" />
|
||||||
|
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="~/defaults/lubelogger_icon_128.png" />
|
||||||
|
<link rel="apple-touch-startup-image" href="~/defaults/lubelogger_launch.png" />
|
||||||
|
<link rel="manifest" href="~/manifest.json">
|
||||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||||
<script src="~/js/shared.js"></script>
|
<script src="~/js/shared.js"></script>
|
||||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
@@ -28,6 +49,11 @@
|
|||||||
enableCsvImport : "@enableCsvImports" == "True"
|
enableCsvImport : "@enableCsvImports" == "True"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function getShortDatePattern() {
|
||||||
|
return {
|
||||||
|
pattern: "@shortDatePattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -8,24 +8,74 @@
|
|||||||
<script src="~/js/gasrecord.js" asp-append-version="true"></script>
|
<script src="~/js/gasrecord.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/collisionrecord.js" asp-append-version="true"></script>
|
<script src="~/js/collisionrecord.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/taxrecord.js" asp-append-version="true"></script>
|
<script src="~/js/taxrecord.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/reminderrecord.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/upgraderecord.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/note.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/reports.js" asp-append-version="true"></script>
|
||||||
<script src="~/lib/chart-js/chart.umd.js"></script>
|
<script src="~/lib/chart-js/chart.umd.js"></script>
|
||||||
}
|
}
|
||||||
|
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||||
|
<ul class="nav navbar-nav" id="vehicleTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="returnToGarage()"><span class="display-3 ms-2"><i class="bi bi-arrow-left-square"></i>Garage</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="editVehicle(@Model.Id)"><span class="display-3 ms-2"><i class="bi bi-pencil-square"></i>Edit Vehicle</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>Fuel</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>Notes</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button onclick="deleteVehicle(@Model.Id)" class="dropdown-item"><span class="display-3 ms-2"><i class="bi bi-trash me-2"></i>Delete Vehicle</span></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<button onclick="returnToGarage()" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></button>
|
<button onclick="returnToGarage()" class="lubelogger-tab btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></button>
|
||||||
<h1 class="text-truncate display-4">@($"{Model.Year} {Model.Make} {Model.Model}")<small class="text-body-secondary">@($"(#{Model.LicensePlate})")</small></h1>
|
<h1 class="text-truncate display-4">@($"{Model.Year} {Model.Make} {Model.Model}")<small class="text-body-secondary">@($"(#{Model.LicensePlate})")</small></h1>
|
||||||
<button onclick="editVehicle(@Model.Id)" class="btn btn-warning btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i></button>
|
<button onclick="editVehicle(@Model.Id)" class="lubelogger-tab btn btn-warning btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i></button>
|
||||||
|
<div class="lubelogger-navbar-button">
|
||||||
|
<button type="button" class="btn btn-dark" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<ul class="nav nav-tabs" id="vehicleTab" role="tablist">
|
<ul class="nav nav-tabs lubelogger-tab" id="vehicleTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
|
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -36,7 +86,7 @@
|
|||||||
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
|
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Reports</button>
|
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown ms-auto" role="presentation">
|
<li class="nav-item dropdown ms-auto" role="presentation">
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Manage Vehicle</a>
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Manage Vehicle</a>
|
||||||
@@ -46,26 +96,14 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" id="vehicleTabContent">
|
<div class="tab-content" id="vehicleTabContent">
|
||||||
<div class="tab-pane fade show active" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="notes-tab-pane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="noteTextArea" class="form-label">This is where you can store notes related to the vehicle such as tire size, oil filter size, oil types, etc.</label>
|
|
||||||
<textarea class="form-control vehicleNoteContainer" id="noteTextArea"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-flex flex-row-reverse">
|
|
||||||
<div>
|
|
||||||
<button onclick="saveVehicleNote(@Model.Id)" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i>Save Note</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
|
<div class="tab-pane fade show active" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
|
<div class="tab-pane fade" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
||||||
@@ -80,8 +118,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="reminderRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="reminderRecordModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function GetVehicleId() {
|
function GetVehicleId() {
|
||||||
return { vehicleId: @Model.Id};
|
return { vehicleId: @Model.Id};
|
||||||
}
|
}
|
||||||
|
bindWindowResize();
|
||||||
</script>
|
</script>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@model string
|
@model ImportMode
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Import Data from CSV</h5>
|
<h5 class="modal-title">Import Data from CSV</h5>
|
||||||
<button type="button" class="btn-close" onclick="hideBulkImportModal()" aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick="hideBulkImportModal()" aria-label="Close"></button>
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.
|
Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.
|
||||||
</div>
|
</div>
|
||||||
@if (Model == "gasrecord")
|
@if (Model == ImportMode.GasRecord)
|
||||||
{
|
{
|
||||||
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
||||||
} else if (Model == "servicerecord" || Model == "repairrecord")
|
} else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
|
||||||
{
|
{
|
||||||
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">Download Sample</a>
|
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">Download Sample</a>
|
||||||
} else if (Model == "taxrecord")
|
} else if (Model == ImportMode.TaxRecord)
|
||||||
{
|
{
|
||||||
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">Download Sample</a>
|
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">Download Sample</a>
|
||||||
}
|
}
|
||||||
@@ -52,14 +52,16 @@
|
|||||||
if (data) {
|
if (data) {
|
||||||
successToast("Data Imported Successfully");
|
successToast("Data Imported Successfully");
|
||||||
hideBulkImportModal();
|
hideBulkImportModal();
|
||||||
if (mode == "gasrecord") {
|
if (mode == "GasRecord") {
|
||||||
getVehicleGasRecords(vehicleId);
|
getVehicleGasRecords(vehicleId);
|
||||||
} else if (mode == "servicerecord") {
|
} else if (mode == "ServiceRecord") {
|
||||||
getVehicleServiceRecords(vehicleId);
|
getVehicleServiceRecords(vehicleId);
|
||||||
} else if (mode == "repairrecord") {
|
} else if (mode == "RepairRecord") {
|
||||||
getVehicleCollisionRecords(vehicleId);
|
getVehicleCollisionRecords(vehicleId);
|
||||||
} else if (mode == "taxrecord") {
|
} else if (mode == "TaxRecord") {
|
||||||
getVehicleTaxRecords(vehicleId);
|
getVehicleTaxRecords(vehicleId);
|
||||||
|
} else if (mode == "UpgradeRecord") {
|
||||||
|
getVehicleUpgradeRecords(vehicleId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorToast("An error has occurred, please double check the data and try again.");
|
errorToast("An error has occurred, please double check the data and try again.");
|
||||||
|
|||||||
72
Views/Vehicle/_Collaborators.cshtml
Normal file
72
Views/Vehicle/_Collaborators.cshtml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
@model List<UserCollaborator>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8">
|
||||||
|
<span class="lead">Collaborators</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<button onclick="addCollaborator()" class="btn btn-link btn-sm"><i class="bi bi-person-add"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-8">Username</th>
|
||||||
|
<th scope="col" class="col-4">Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (UserCollaborator user in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex">
|
||||||
|
<td class="col-8">@user.UserName</td>
|
||||||
|
<td class="col-4">
|
||||||
|
@if(User.Identity.Name != user.UserName)
|
||||||
|
{
|
||||||
|
<button onclick="deleteCollaborator(@user.UserVehicle.UserId, @user.UserVehicle.VehicleId)" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function deleteCollaborator(userId, vehicleId) {
|
||||||
|
$.post('/Vehicle/DeleteCollaboratorFromVehicle', {userId: userId, vehicleId: vehicleId}, function(data){
|
||||||
|
if (data) {
|
||||||
|
refreshCollaborators();
|
||||||
|
} else {
|
||||||
|
errorToast("An error occurred, please try again later");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function addCollaborator() {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Add Collaborator',
|
||||||
|
html: `
|
||||||
|
<input type="text" id="inputUserName" class="swal2-input" placeholder="Username">
|
||||||
|
`,
|
||||||
|
confirmButtonText: 'Add',
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const userName = $("#inputUserName").val();
|
||||||
|
if (!userName) {
|
||||||
|
Swal.showValidationMessage(`Please enter a username`);
|
||||||
|
}
|
||||||
|
return { userName }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
$.post('/Vehicle/AddCollaboratorsToVehicle', { username: result.value.userName, vehicleId: vehicleId }, function (data) {
|
||||||
|
if (data.success) {
|
||||||
|
refreshCollaborators();
|
||||||
|
} else {
|
||||||
|
errorToast(data.message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
@model CollisionRecordInput
|
@model CollisionRecordInput
|
||||||
|
@{
|
||||||
|
var isNew = Model.Id == 0;
|
||||||
|
}
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">@(Model.Id == 0 ? "Add New Repair Record" : "Edit Repair Record")</h5>
|
<h5 class="modal-title">@(isNew ? "Add New Repair Record" : "Edit Repair Record")</h5>
|
||||||
<button type="button" class="btn-close" onclick="hideAddCollisionRecordModal()" aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick="hideAddCollisionRecordModal()" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -11,15 +14,15 @@
|
|||||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||||
<label for="collisionRecordDate">Date</label>
|
<label for="collisionRecordDate">Date</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" id="collisionRecordDate" class="form-control" value="@Model.Date">
|
<input type="text" id="collisionRecordDate" class="form-control" placeholder="Date repair was performed" value="@Model.Date">
|
||||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<label for="collisionRecordMileage">Odometer</label>
|
<label for="collisionRecordMileage">Odometer</label>
|
||||||
<input type="number" id="collisionRecordMileage" class="form-control" value="@Model.Mileage">
|
<input type="number" id="collisionRecordMileage" class="form-control" placeholder="Odometer reading when repaired" value="@(isNew ? "" : Model.Mileage)">
|
||||||
<label for="collisionRecordDescription">Description</label>
|
<label for="collisionRecordDescription">Description</label>
|
||||||
<input type="text" id="collisionRecordDescription" class="form-control" value="@Model.Description">
|
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="Description of item(s) repaired(i.e. Alternator)" value="@Model.Description">
|
||||||
<label for="collisionRecordCost">Cost</label>
|
<label for="collisionRecordCost">Cost</label>
|
||||||
<input type="number" id="collisionRecordCost" class="form-control" value="@Model.Cost">
|
<input type="text" id="collisionRecordCost" class="form-control" placeholder="Cost of the repair" value="@(isNew ? "" : Model.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="collisionRecordNotes">Notes(optional)</label>
|
<label for="collisionRecordNotes">Notes(optional)</label>
|
||||||
@@ -27,20 +30,22 @@
|
|||||||
@if (Model.Files.Any())
|
@if (Model.Files.Any())
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
<label>Uploaded Documents</label>
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
|
||||||
{
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<a type="button" class="btn btn-link" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteCollisionRecordFile('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<label for="collisionRecordFiles">Upload more documents</label>
|
<label for="collisionRecordFiles">Upload more documents</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@if (isNew)
|
||||||
|
{
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
|
||||||
|
<label class="form-check-label" for="addReminderCheck">
|
||||||
|
Add Reminder
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<label for="collisionRecordFiles">Upload documents(optional)</label>
|
<label for="collisionRecordFiles">Upload documents(optional)</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
||||||
}
|
}
|
||||||
@@ -50,16 +55,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (Model.Id > 0)
|
@if (!isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-danger" onclick="deleteCollisionRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
<button type="button" class="btn btn-danger" onclick="deleteCollisionRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">Cancel</button>
|
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">Cancel</button>
|
||||||
@if (Model.Id == 0)
|
@if (isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">Add New Repair Record</button>
|
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">Add New Repair Record</button>
|
||||||
}
|
}
|
||||||
else if (Model.Id > 0)
|
else if (!isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">Edit Repair Record</button>
|
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">Edit Repair Record</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@inject IConfiguration Configuration
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
@{
|
@{
|
||||||
var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]);
|
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||||
|
var hideZero = config.GetUserConfig(User).HideZero;
|
||||||
}
|
}
|
||||||
@model List<CollisionRecord>
|
@model List<CollisionRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -18,7 +20,8 @@
|
|||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('repairrecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -31,6 +34,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row vehicleDetailTabContainer">
|
<div class="row vehicleDetailTabContainer">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
<div class="row mt-2 showOnPrint">
|
||||||
|
<div class="d-flex">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
@@ -48,8 +56,8 @@
|
|||||||
<td class="col-1">@collisionRecord.Date.ToShortDateString()</td>
|
<td class="col-1">@collisionRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-2">@collisionRecord.Mileage</td>
|
<td class="col-2">@collisionRecord.Mileage</td>
|
||||||
<td class="col-4">@collisionRecord.Description</td>
|
<td class="col-4">@collisionRecord.Description</td>
|
||||||
<td class="col-2">@collisionRecord.Cost.ToString("C")</td>
|
<td class="col-2">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@collisionRecord.Notes</td>
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -58,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="modal fade" id="collisionRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" data-bs-focus="false" id="collisionRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="collisionRecordModalContent">
|
<div class="modal-content" id="collisionRecordModalContent">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@model CostMakeUpForVehicle
|
@model CostMakeUpForVehicle
|
||||||
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum > 0)
|
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum + Model.UpgradeRecordSum > 0)
|
||||||
{
|
{
|
||||||
<canvas id="pie-chart"></canvas>
|
<canvas id="pie-chart"></canvas>
|
||||||
<script>
|
<script>
|
||||||
@@ -9,17 +9,18 @@
|
|||||||
new Chart($("#pie-chart"), {
|
new Chart($("#pie-chart"), {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Tax", "Fuel"],
|
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Upgrades", "Tax", "Fuel"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Expenses by Category",
|
label: "Expenses by Category",
|
||||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361"],
|
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
||||||
data: [
|
data: [
|
||||||
@Model.ServiceRecordSum,
|
@Model.ServiceRecordSum,
|
||||||
@Model.CollisionRecordSum,
|
@Model.CollisionRecordSum,
|
||||||
|
@Model.UpgradeRecordSum,
|
||||||
@Model.TaxRecordSum,
|
@Model.TaxRecordSum,
|
||||||
@Model.GasRecordSum
|
@Model.GasRecordSum
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -41,7 +42,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
} else
|
}
|
||||||
{
|
else
|
||||||
<h1>No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.</h1>
|
{
|
||||||
|
<div class="text-center">
|
||||||
|
<h4>No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.</h4>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,48 @@
|
|||||||
@inject IConfiguration Configuration
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@model GasRecordViewModelContainer
|
||||||
@{
|
@{
|
||||||
var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]);
|
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||||
var useMPG = bool.Parse(Configuration[nameof(UserConfig.UseMPG)]);
|
var useMPG = config.GetUserConfig(User).UseMPG;
|
||||||
|
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
|
||||||
|
var hideZero = config.GetUserConfig(User).HideZero;
|
||||||
|
var useKwh = Model.UseKwh;
|
||||||
|
string consumptionUnit;
|
||||||
|
string fuelEconomyUnit;
|
||||||
|
string distanceUnit = useMPG ? "mi." : "km";
|
||||||
|
if (useKwh)
|
||||||
|
{
|
||||||
|
consumptionUnit = "kWh";
|
||||||
|
fuelEconomyUnit = useMPG ? "mi./kWh" : "kWh/100km";
|
||||||
|
}
|
||||||
|
else if (useMPG && useUKMPG)
|
||||||
|
{
|
||||||
|
consumptionUnit = "imp gal";
|
||||||
|
fuelEconomyUnit = "mpg";
|
||||||
|
} else if (useUKMPG)
|
||||||
|
{
|
||||||
|
fuelEconomyUnit = "l/100mi.";
|
||||||
|
consumptionUnit = "l";
|
||||||
|
distanceUnit = "mi.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consumptionUnit = useMPG ? "US gal" : "l";
|
||||||
|
fuelEconomyUnit = useMPG ? "mpg" : "l/100km";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@model List<GasRecordViewModel>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.Count()}")</span>
|
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
|
||||||
@if (Model.Count() > 1)
|
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
|
||||||
{
|
{
|
||||||
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {Model.Where(y => y.MilesPerGallon > 0)?.Average(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Average(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
<span class="ms-2 badge bg-primary">@($"Min Fuel Economy: {Model.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary">@($"Min Fuel Economy: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
<span class="ms-2 badge bg-primary">@($"Max Fuel Economy: {Model.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary">@($"Max Fuel Economy: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
}
|
}
|
||||||
<span class="ms-2 badge bg-success">@($"Total Fuel Consumed: {Model.Sum(x=>x.Gallons).ToString("F")}")</span>
|
<span class="ms-2 badge bg-success">@($"Total Fuel Consumed: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||||
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.GasRecords.Sum(x => x.Cost).ToString("C3")}")</span>
|
||||||
</div>
|
</div>
|
||||||
@if (enableCsvImports)
|
@if (enableCsvImports)
|
||||||
{
|
{
|
||||||
@@ -25,7 +52,8 @@
|
|||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('gasrecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
@@ -35,27 +63,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row vehicleDetailTabContainer">
|
<div class="row vehicleDetailTabContainer">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
<div class="row mt-2 showOnPrint">
|
||||||
|
<div class="d-flex">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2">Date Refueled</th>
|
<th scope="col" class="col-2">Date Refueled</th>
|
||||||
<th scope="col" class="col-2">Odometer(@(useMPG ? "mi." : "km"))</th>
|
<th scope="col" class="col-2">Odometer(@(distanceUnit))</th>
|
||||||
<th scope="col" class="col-2">Consumption(@(useMPG ? "gal" : "l"))</th>
|
<th scope="col" class="col-2">Consumption(@(consumptionUnit))</th>
|
||||||
<th scope="col" class="col-2">Fuel Economy(@(useMPG ? "mpg" : "l/100km"))</th>
|
<th scope="col" class="col-4">Fuel Economy(@(fuelEconomyUnit))</th>
|
||||||
<th scope="col" class="col-2">Cost</th>
|
<th scope="col" class="col-1">Cost</th>
|
||||||
<th scope="col" class="col-2">Unit Cost</th>
|
<th scope="col" class="col-1">Unit Cost</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (GasRecordViewModel gasRecord in Model)
|
@foreach (GasRecordViewModel gasRecord in Model.GasRecords)
|
||||||
{
|
{
|
||||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditGasRecordModal(@gasRecord.Id)">
|
<tr class="d-flex" style="cursor:pointer;" onclick="showEditGasRecordModal(@gasRecord.Id)">
|
||||||
<td class="col-2">@gasRecord.Date</td>
|
<td class="col-2">@gasRecord.Date</td>
|
||||||
<td class="col-2">@gasRecord.Mileage</td>
|
<td class="col-2">@gasRecord.Mileage</td>
|
||||||
<td class="col-2">@gasRecord.Gallons.ToString("F")</td>
|
<td class="col-2">@gasRecord.Gallons.ToString("F")</td>
|
||||||
<td class="col-2">@gasRecord.MilesPerGallon.ToString("F")</td>
|
<td class="col-4">@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||||
<td class="col-2">@gasRecord.Cost.ToString("C")</td>
|
<td class="col-1">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString("C3"))</td>
|
||||||
<td class="col-2">@gasRecord.CostPerGallon.ToString("C")</td>
|
<td class="col-1">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString("C3"))</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -64,7 +97,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="modal fade" id="gasRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" data-bs-focus="false" id="gasRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="gasRecordModalContent">
|
<div class="modal-content" id="gasRecordModalContent">
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
@model List<GasCostForVehicleByMonth>
|
@model List<CostForVehicleByMonth>
|
||||||
@if (Model.Any())
|
@if (Model.Any())
|
||||||
{
|
{
|
||||||
<canvas id="bar-chart" class="vehicleDetailTabContainer"></canvas>
|
<canvas id="bar-chart"></canvas>
|
||||||
<script>
|
<script>
|
||||||
renderChart();
|
renderChart();
|
||||||
function renderChart() {
|
function renderChart() {
|
||||||
var barGraphLabels = [];
|
var barGraphLabels = [];
|
||||||
var barGraphData = [];
|
var barGraphData = [];
|
||||||
var useDarkMode = getGlobalConfig().useDarkMode;
|
var useDarkMode = getGlobalConfig().useDarkMode;
|
||||||
@foreach (GasCostForVehicleByMonth gasCost in Model)
|
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||||
{
|
{
|
||||||
@:barGraphLabels.push("@gasCost.MonthName");
|
@:barGraphLabels.push("@gasCost.MonthName");
|
||||||
@:barGraphData.push(@gasCost.Cost);
|
@:barGraphData.push(@gasCost.Cost);
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
labels: barGraphLabels,
|
labels: barGraphLabels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Gas Expenses by Month",
|
label: "Expenses by Month",
|
||||||
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||||
data: barGraphData
|
data: barGraphData
|
||||||
}
|
}
|
||||||
@@ -52,5 +52,7 @@
|
|||||||
</script>
|
</script>
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
<h1>No data found, insert some gas data to see visualizations here.</h1>
|
<div class="text-center">
|
||||||
|
<h4>No data found, insert/select some data to see visualizations here.</h4>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,35 @@
|
|||||||
@inject IConfiguration Configuration
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@model GasRecordInputContainer
|
||||||
@{
|
@{
|
||||||
var useMPG = bool.Parse(Configuration[nameof(UserConfig.UseMPG)]);
|
var useMPG = config.GetUserConfig(User).UseMPG;
|
||||||
|
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
|
||||||
|
var useKwh = Model.UseKwh;
|
||||||
|
var isNew = Model.GasRecord.Id == 0;
|
||||||
|
string consumptionUnit;
|
||||||
|
string distanceUnit;
|
||||||
|
if (useKwh)
|
||||||
|
{
|
||||||
|
consumptionUnit = "kWh";
|
||||||
|
} else if (useUKMPG)
|
||||||
|
{
|
||||||
|
consumptionUnit = "liters";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
consumptionUnit = useMPG ? "gallons" : "liters";
|
||||||
|
}
|
||||||
|
if (useUKMPG)
|
||||||
|
{
|
||||||
|
distanceUnit = "miles";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
distanceUnit = useMPG ? "miles" : "kilometers";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@model GasRecordInput
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">@(Model.Id == 0 ? "Add New Gas Record" : "Edit Gas Record")</h5>
|
<h5 class="modal-title">@(isNew ? "Add New Gas Record" : "Edit Gas Record")</h5>
|
||||||
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -15,28 +40,29 @@
|
|||||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||||
<label for="gasRecordDate">Date</label>
|
<label for="gasRecordDate">Date</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" id="gasRecordDate" class="form-control" value="@Model.Date">
|
<input type="text" id="gasRecordDate" placeholder="Date refueled" class="form-control" value="@Model.GasRecord.Date">
|
||||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<label for="gasRecordMileage">Odometer Reading(@(useMPG ? "miles" : "kilometers"))</label>
|
<label for="gasRecordMileage">Odometer Reading(@distanceUnit)</label>
|
||||||
<input type="number" id="gasRecordMileage" class="form-control" value="@Model.Mileage">
|
<input type="number" id="gasRecordMileage" class="form-control" placeholder="Odometer reading when refueled" value="@(isNew ? "" : Model.GasRecord.Mileage)">
|
||||||
<label for="gasRecordGallons">Fuel Consumption(@(useMPG ? "gallons" : "liters"))</label>
|
<label for="gasRecordGallons">Fuel Consumption(@(consumptionUnit))</label>
|
||||||
<input type="text" id="gasRecordGallons" class="form-control" value="@Model.Gallons">
|
<input type="text" id="gasRecordGallons" class="form-control" placeholder="Amount of gas refueled" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
||||||
|
<label class="form-check-label" for="gasIsFillToFull">Is Filled To Full</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="gasIsMissed" checked="@Model.GasRecord.MissedFuelUp">
|
||||||
|
<label class="form-check-label" for="gasIsMissed">Missed Fuel Up(Skip MPG Calculation)</label>
|
||||||
|
</div>
|
||||||
<label for="GasRecordCost">Cost</label>
|
<label for="GasRecordCost">Cost</label>
|
||||||
<input type="number" id="gasRecordCost" class="form-control" value="@Model.Cost">
|
<input type="text" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
@if (Model.Files.Any())
|
@if (Model.GasRecord.Files.Any())
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
<label>Uploaded Documents</label>
|
@await Html.PartialAsync("_UploadedFiles", Model.GasRecord.Files)
|
||||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
|
||||||
{
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<a type="button" class="btn btn-link" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteGasRecordFile('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<label for="gasRecordFiles">Upload more documents</label>
|
<label for="gasRecordFiles">Upload more documents</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
||||||
</div>
|
</div>
|
||||||
@@ -52,16 +78,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (Model.Id > 0)
|
@if (!isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-danger" onclick="deleteGasRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
<button type="button" class="btn btn-danger" onclick="deleteGasRecord(@Model.GasRecord.Id)" style="margin-right:auto;">Delete</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">Cancel</button>
|
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">Cancel</button>
|
||||||
@if (Model.Id == 0)
|
@if (isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle()">Add New Gas Record</button>
|
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle()">Add New Gas Record</button>
|
||||||
}
|
}
|
||||||
else if (Model.Id > 0)
|
else if (!isNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle(true)">Edit Gas Record</button>
|
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle(true)">Edit Gas Record</button>
|
||||||
}
|
}
|
||||||
@@ -70,12 +96,12 @@
|
|||||||
var uploadedFiles = [];
|
var uploadedFiles = [];
|
||||||
getUploadedFilesFromModel();
|
getUploadedFilesFromModel();
|
||||||
function getUploadedFilesFromModel() {
|
function getUploadedFilesFromModel() {
|
||||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
@foreach (UploadedFiles filesUploaded in Model.GasRecord.Files)
|
||||||
{
|
{
|
||||||
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getGasRecordModelData(){
|
function getGasRecordModelData(){
|
||||||
return {id: @Model.Id}
|
return { id: @Model.GasRecord.Id}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
58
Views/Vehicle/_MPGByMonthReport.cshtml
Normal file
58
Views/Vehicle/_MPGByMonthReport.cshtml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
@model List<CostForVehicleByMonth>
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<canvas id="bar-chart-mpg"></canvas>
|
||||||
|
<script>
|
||||||
|
renderChart();
|
||||||
|
function renderChart() {
|
||||||
|
var barGraphLabels = [];
|
||||||
|
var barGraphData = [];
|
||||||
|
var useDarkMode = getGlobalConfig().useDarkMode;
|
||||||
|
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||||
|
{
|
||||||
|
@:barGraphLabels.push("@gasCost.MonthName");
|
||||||
|
@:barGraphData.push(@gasCost.Cost);
|
||||||
|
}
|
||||||
|
new Chart($("#bar-chart-mpg"), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: barGraphLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Fuel Mileage by Month",
|
||||||
|
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||||
|
data: barGraphData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: useDarkMode ? "#fff" : "#000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
color: useDarkMode ? "#fff" : "#000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
color: useDarkMode ? "#fff" : "#000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
<div class="text-center">
|
||||||
|
<h4>No data found, insert/select some data to see visualizations here.</h4>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
45
Views/Vehicle/_NoteModal.cshtml
Normal file
45
Views/Vehicle/_NoteModal.cshtml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@model Note
|
||||||
|
@{
|
||||||
|
var isNew = Model.Id == 0;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@(isNew ? "Add New Note" : "Edit Note")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideAddNoteModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||||
|
<label for="noteDescription">Description</label>
|
||||||
|
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="noteTextArea">Notes</label>
|
||||||
|
<textarea class="form-control vehicleNoteContainer" id="noteTextArea">@Model.NoteText</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" onclick="deleteNote(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideAddNoteModal()">Cancel</button>
|
||||||
|
@if (isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle()">Add New Note</button>
|
||||||
|
}
|
||||||
|
else if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle(true)">Edit Note</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function getNoteModelData(){
|
||||||
|
return { id: @Model.Id}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
45
Views/Vehicle/_Notes.cshtml
Normal file
45
Views/Vehicle/_Notes.cshtml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@model List<Note>
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
<span class="ms-2 badge bg-success">@($"# of Notes: {Model.Count()}")</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row vehicleDetailTabContainer">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row mt-2 showOnPrint">
|
||||||
|
<div class="d-flex">
|
||||||
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-3">Description</th>
|
||||||
|
<th scope="col" class="col-9">Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (Note note in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
|
||||||
|
<td class="col-3">@note.Description</td>
|
||||||
|
<td class="col-9 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="noteModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content" id="noteModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user