Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92b9c6953c | ||
|
|
ac1c2ca7fe | ||
|
|
f05ca7f1f8 | ||
|
|
ecef1c8640 | ||
|
|
43d58a4b7e | ||
|
|
8293b376a2 | ||
|
|
c243cdd54c | ||
|
|
0383257356 | ||
|
|
73e8731bd4 | ||
|
|
b0e82889e0 | ||
|
|
8e28116371 | ||
|
|
ed717af91c | ||
|
|
7ae5a982e9 | ||
|
|
2e3f1e6763 | ||
|
|
4a2e02afc8 | ||
|
|
9c53850faa | ||
|
|
695b15faed | ||
|
|
33aeaf9825 | ||
|
|
dc3608524a | ||
|
|
6996814888 | ||
|
|
9bbaf49ade | ||
|
|
db737df712 | ||
|
|
bae1839c06 | ||
|
|
7e70f5108f | ||
|
|
c5c0a6900b | ||
|
|
50b88dd7a1 | ||
|
|
e44eb991f5 | ||
|
|
39d0d346ad | ||
|
|
142865f1ce | ||
|
|
2c18755a0f | ||
|
|
d3be77bf09 | ||
|
|
bcafd9e97e | ||
|
|
f581e55842 | ||
|
|
dfe4e4f1ab | ||
|
|
080e2a1667 | ||
|
|
01cbee00fd | ||
|
|
f0869c741f | ||
|
|
6dc690011d | ||
|
|
65891c7c11 | ||
|
|
d306152d38 | ||
|
|
b372f64e11 | ||
|
|
baff93bd96 | ||
|
|
abbe13b269 | ||
|
|
fb43932e0d | ||
|
|
c10aa73e5e | ||
|
|
be09f3e9dd | ||
|
|
d3f94337ae | ||
|
|
46a227453e | ||
|
|
8981d69844 | ||
|
|
09fbc37472 | ||
|
|
5cee1ea7cf | ||
|
|
cd9a673e62 | ||
|
|
dd9f754e85 | ||
|
|
7d22d1ddc4 | ||
|
|
d5f562b48f | ||
|
|
bb64ee4012 | ||
|
|
a60430b4b2 | ||
|
|
117a172bc2 | ||
|
|
b93b7f321e | ||
|
|
f34f3da587 | ||
|
|
8104f852d8 | ||
|
|
32e58e6280 | ||
|
|
c4dc81e4bc | ||
|
|
9fe0396abe | ||
|
|
ad2b58697a | ||
|
|
4845b02fc8 | ||
|
|
1ed53d5e4b | ||
|
|
00622126d7 | ||
|
|
b5cdd66248 | ||
|
|
78408427b8 | ||
|
|
baf9e8e833 | ||
|
|
bde00a26d5 | ||
|
|
6be6a2196e | ||
|
|
b8dab3d4a4 | ||
|
|
447c929386 | ||
|
|
9e37c01a83 | ||
|
|
3d39dc8ed8 | ||
|
|
08ace8b08d | ||
|
|
cbf95b1e04 | ||
|
|
fd8f93ee5f | ||
|
|
4f146f9427 | ||
|
|
19ace06c08 | ||
|
|
4bfe947ce1 | ||
|
|
4850b38e7f | ||
|
|
e5f19c5f20 | ||
|
|
8c4212678f | ||
|
|
573e80728c | ||
|
|
c88fa690f6 | ||
|
|
1feb89acf5 | ||
|
|
8be252e949 | ||
|
|
3aaf230a60 | ||
|
|
e45339f75c | ||
|
|
9c225ff7a0 | ||
|
|
6cdfba420f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,3 +8,6 @@ wwwroot/documents/
|
||||
wwwroot/temp/
|
||||
wwwroot/imports/
|
||||
wwwroot/translations/
|
||||
config/userConfig.json
|
||||
CarCareTracker.csproj.user
|
||||
Properties/launchSettings.json
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<View_SelectedScaffolderID>RazorViewEmptyScaffolder</View_SelectedScaffolderID>
|
||||
<View_SelectedScaffolderCategoryPath>root/Common/MVC/View</View_SelectedScaffolderCategoryPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -17,18 +17,26 @@ namespace CarCareTracker.Controllers
|
||||
private readonly IUserLogic _userLogic;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly IConfigHelper _config;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger,
|
||||
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||
private readonly IReminderHelper _reminderHelper;
|
||||
public HomeController(ILogger<HomeController> logger,
|
||||
IVehicleDataAccess dataAccess,
|
||||
IUserLogic userLogic,
|
||||
IConfigHelper configuration,
|
||||
IFileHelper fileHelper)
|
||||
IFileHelper fileHelper,
|
||||
IExtraFieldDataAccess extraFieldDataAccess,
|
||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||
IReminderHelper reminderHelper)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataAccess = dataAccess;
|
||||
_config = configuration;
|
||||
_userLogic = userLogic;
|
||||
_fileHelper = fileHelper;
|
||||
_extraFieldDataAccess = extraFieldDataAccess;
|
||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||
_reminderHelper = reminderHelper;
|
||||
}
|
||||
private int GetUserID()
|
||||
{
|
||||
@@ -47,6 +55,29 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return PartialView("_GarageDisplay", vehiclesStored);
|
||||
}
|
||||
public IActionResult Calendar()
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
||||
foreach(Vehicle vehicle in vehiclesStored)
|
||||
{
|
||||
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
||||
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
||||
if (vehicleReminders.Any())
|
||||
{
|
||||
var maxMileage = vehicleReminders.Max(x => x.Mileage) + 1000;
|
||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, maxMileage, DateTime.Now);
|
||||
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate} - {x.Description}" }).ToList();
|
||||
reminders.AddRange(reminderUrgency);
|
||||
}
|
||||
}
|
||||
return PartialView("_Calendar", reminders);
|
||||
}
|
||||
public IActionResult Settings()
|
||||
{
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
@@ -68,7 +99,28 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult GetExtraFieldsModal(int importMode = 0)
|
||||
{
|
||||
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(importMode);
|
||||
if (recordExtraFields.Id != importMode)
|
||||
{
|
||||
recordExtraFields.Id = importMode;
|
||||
}
|
||||
return PartialView("_ExtraFields", recordExtraFields);
|
||||
}
|
||||
public IActionResult UpdateExtraFields(RecordExtraField record)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _extraFieldDataAccess.SaveExtraFields(record);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(record.Id);
|
||||
return PartialView("_ExtraFields", recordExtraFields);
|
||||
}
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
||||
757
Controllers/MigrationController.cs
Normal file
757
Controllers/MigrationController.cs
Normal file
@@ -0,0 +1,757 @@
|
||||
using CarCareTracker.External.Implementations;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Npgsql;
|
||||
using System.Data.Common;
|
||||
using System.IO.Compression;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
{
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
public class MigrationController : Controller
|
||||
{
|
||||
private IConfigHelper _configHelper;
|
||||
private IFileHelper _fileHelper;
|
||||
private readonly ILogger<MigrationController> _logger;
|
||||
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, IConfiguration serverConfig, ILogger<MigrationController> logger)
|
||||
{
|
||||
_configHelper = configHelper;
|
||||
_fileHelper = fileHelper;
|
||||
_logger = logger;
|
||||
}
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
|
||||
{
|
||||
return View();
|
||||
} else
|
||||
{
|
||||
return new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
}
|
||||
private void InitializeTables(NpgsqlDataSource conn)
|
||||
{
|
||||
var cmds = new List<string>
|
||||
{
|
||||
"CREATE TABLE IF NOT EXISTS app.vehicles (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.collisionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.upgraderecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.servicerecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.gasrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.notes (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.odometerrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.reminderrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.planrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.planrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.supplyrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.taxrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.userrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)",
|
||||
"CREATE TABLE IF NOT EXISTS app.tokenrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.userconfigrecords (id INT primary key, data jsonb not null)",
|
||||
"CREATE TABLE IF NOT EXISTS app.useraccessrecords (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))",
|
||||
"CREATE TABLE IF NOT EXISTS app.extrafields (id INT primary key, data jsonb not null)"
|
||||
};
|
||||
foreach(string cmd in cmds)
|
||||
{
|
||||
using (var ctext = conn.CreateCommand(cmd))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
public IActionResult Export()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
|
||||
}
|
||||
var tempFolder = $"temp/{Guid.NewGuid()}";
|
||||
var tempPath = $"{tempFolder}/cartracker.db";
|
||||
var fullFolderPath = _fileHelper.GetFullFilePath(tempFolder, false);
|
||||
Directory.CreateDirectory(fullFolderPath);
|
||||
var fullFileName = _fileHelper.GetFullFilePath(tempPath, false);
|
||||
try
|
||||
{
|
||||
var pgDataSource = NpgsqlDataSource.Create(_configHelper.GetServerPostgresConnection());
|
||||
InitializeTables(pgDataSource);
|
||||
//pull records
|
||||
var vehicles = new List<Vehicle>();
|
||||
var repairrecords = new List<CollisionRecord>();
|
||||
var upgraderecords = new List<UpgradeRecord>();
|
||||
var servicerecords = new List<ServiceRecord>();
|
||||
|
||||
var gasrecords = new List<GasRecord>();
|
||||
var noterecords = new List<Note>();
|
||||
var odometerrecords = new List<OdometerRecord>();
|
||||
var reminderrecords = new List<ReminderRecord>();
|
||||
|
||||
var planrecords = new List<PlanRecord>();
|
||||
var planrecordtemplates = new List<PlanRecordInput>();
|
||||
var supplyrecords = new List<SupplyRecord>();
|
||||
var taxrecords = new List<TaxRecord>();
|
||||
|
||||
var userrecords = new List<UserData>();
|
||||
var tokenrecords = new List<Token>();
|
||||
var userconfigrecords = new List<UserConfigData>();
|
||||
var useraccessrecords = new List<UserAccess>();
|
||||
|
||||
var extrafields = new List<RecordExtraField>();
|
||||
#region "Part1"
|
||||
string cmd = $"SELECT data FROM app.vehicles";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Vehicle vehicle = System.Text.Json.JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
vehicles.Add(vehicle);
|
||||
}
|
||||
}
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Vehicle>("vehicles");
|
||||
table.Upsert(vehicle);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.collisionrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
repairrecords.Add(System.Text.Json.JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in repairrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<CollisionRecord>("collisionrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.upgraderecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
upgraderecords.Add(System.Text.Json.JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in upgraderecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UpgradeRecord>("upgraderecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.servicerecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
servicerecords.Add(System.Text.Json.JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in servicerecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<ServiceRecord>("servicerecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
#region "Part2"
|
||||
cmd = $"SELECT data FROM app.gasrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
gasrecords.Add(System.Text.Json.JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in gasrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<GasRecord>("gasrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.notes";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
noterecords.Add(System.Text.Json.JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in noterecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Note>("notes");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.odometerrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
odometerrecords.Add(System.Text.Json.JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in odometerrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>("odometerrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.reminderrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
reminderrecords.Add(System.Text.Json.JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in reminderrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<ReminderRecord>("reminderrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
#region "Part3"
|
||||
cmd = $"SELECT data FROM app.planrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecords.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>("planrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.planrecordtemplates";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecordtemplates.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecordtemplates)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecordInput>("planrecordtemplates");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.supplyrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
supplyrecords.Add(System.Text.Json.JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in supplyrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>("supplyrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.taxrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
taxrecords.Add(System.Text.Json.JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in taxrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<TaxRecord>("taxrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
#region "Part4"
|
||||
cmd = $"SELECT id, username, emailaddress, password, isadmin FROM app.userrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserData result = new UserData();
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.UserName = reader["username"].ToString();
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Password = reader["password"].ToString();
|
||||
result.IsAdmin = bool.Parse(reader["isadmin"].ToString());
|
||||
userrecords.Add(result);
|
||||
}
|
||||
}
|
||||
foreach (var record in userrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserData>("userrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT id, emailaddress, body FROM app.tokenrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Token result = new Token();
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Body = reader["body"].ToString();
|
||||
tokenrecords.Add(result);
|
||||
}
|
||||
}
|
||||
foreach (var record in tokenrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Token>("tokenrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT data FROM app.userconfigrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
userconfigrecords.Add(System.Text.Json.JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in userconfigrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserConfigData>("userconfigrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
cmd = $"SELECT userId, vehicleId FROM app.useraccessrecords";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserAccess result = new UserAccess()
|
||||
{
|
||||
Id = new UserVehicle
|
||||
{
|
||||
UserId = int.Parse(reader["userId"].ToString()),
|
||||
VehicleId = int.Parse(reader["vehicleId"].ToString())
|
||||
}
|
||||
};
|
||||
useraccessrecords.Add(result);
|
||||
}
|
||||
}
|
||||
foreach (var record in useraccessrecords)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserAccess>("useraccessrecords");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
#region "Part5"
|
||||
cmd = $"SELECT data FROM app.extrafields";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
extrafields.Add(System.Text.Json.JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in extrafields)
|
||||
{
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<RecordExtraField>("extrafields");
|
||||
table.Upsert(record);
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
var destFilePath = $"{fullFolderPath}.zip";
|
||||
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
|
||||
return Json(new OperationResponse { Success = true, Message = $"/{tempFolder}.zip" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
}
|
||||
}
|
||||
public IActionResult Import(string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
|
||||
}
|
||||
var fullFileName = _fileHelper.GetFullFilePath(fileName);
|
||||
if (string.IsNullOrWhiteSpace(fullFileName))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
}
|
||||
try
|
||||
{
|
||||
var pgDataSource = NpgsqlDataSource.Create(_configHelper.GetServerPostgresConnection());
|
||||
InitializeTables(pgDataSource);
|
||||
//pull records
|
||||
var vehicles = new List<Vehicle>();
|
||||
var repairrecords = new List<CollisionRecord>();
|
||||
var upgraderecords = new List<UpgradeRecord>();
|
||||
var servicerecords = new List<ServiceRecord>();
|
||||
|
||||
var gasrecords = new List<GasRecord>();
|
||||
var noterecords = new List<Note>();
|
||||
var odometerrecords = new List<OdometerRecord>();
|
||||
var reminderrecords = new List<ReminderRecord>();
|
||||
|
||||
var planrecords = new List<PlanRecord>();
|
||||
var planrecordtemplates = new List<PlanRecordInput>();
|
||||
var supplyrecords = new List<SupplyRecord>();
|
||||
var taxrecords = new List<TaxRecord>();
|
||||
|
||||
var userrecords = new List<UserData>();
|
||||
var tokenrecords = new List<Token>();
|
||||
var userconfigrecords = new List<UserConfigData>();
|
||||
var useraccessrecords = new List<UserAccess>();
|
||||
|
||||
var extrafields = new List<RecordExtraField>();
|
||||
#region "Part1"
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Vehicle>("vehicles");
|
||||
vehicles = table.FindAll().ToList();
|
||||
};
|
||||
foreach(var vehicle in vehicles)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.vehicles (id, data) VALUES(@id, CAST(@data AS jsonb)); SELECT setval('app.vehicles_id_seq', (SELECT MAX(id) from app.vehicles));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(vehicle));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<CollisionRecord>("collisionrecords");
|
||||
repairrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in repairrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.collisionrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.collisionrecords_id_seq', (SELECT MAX(id) from app.collisionrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<ServiceRecord>("servicerecords");
|
||||
servicerecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in servicerecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.servicerecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.servicerecords_id_seq', (SELECT MAX(id) from app.servicerecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UpgradeRecord>("upgraderecords");
|
||||
upgraderecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in upgraderecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.upgraderecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.upgraderecords_id_seq', (SELECT MAX(id) from app.upgraderecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region "Part2"
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<GasRecord>("gasrecords");
|
||||
gasrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in gasrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.gasrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.gasrecords_id_seq', (SELECT MAX(id) from app.gasrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Note>("notes");
|
||||
noterecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in noterecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.notes (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.notes_id_seq', (SELECT MAX(id) from app.notes));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>("odometerrecords");
|
||||
odometerrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in odometerrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.odometerrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.odometerrecords_id_seq', (SELECT MAX(id) from app.odometerrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<ReminderRecord>("reminderrecords");
|
||||
reminderrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in reminderrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.reminderrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.reminderrecords_id_seq', (SELECT MAX(id) from app.reminderrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region "Part3"
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>("planrecords");
|
||||
planrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in planrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.planrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.planrecords_id_seq', (SELECT MAX(id) from app.planrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecordInput>("planrecordtemplates");
|
||||
planrecordtemplates = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in planrecordtemplates)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.planrecordtemplates (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.planrecordtemplates_id_seq', (SELECT MAX(id) from app.planrecordtemplates));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>("supplyrecords");
|
||||
supplyrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in supplyrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.supplyrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.supplyrecords_id_seq', (SELECT MAX(id) from app.supplyrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<TaxRecord>("taxrecords");
|
||||
taxrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in taxrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.taxrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.taxrecords_id_seq', (SELECT MAX(id) from app.taxrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region "Part4"
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserData>("userrecords");
|
||||
userrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in userrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.userrecords (id, username, emailaddress, password, isadmin) VALUES(@id, @username, @emailaddress, @password, @isadmin); SELECT setval('app.userrecords_id_seq', (SELECT MAX(id) from app.userrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("username", record.UserName);
|
||||
ctext.Parameters.AddWithValue("emailaddress", record.EmailAddress);
|
||||
ctext.Parameters.AddWithValue("password", record.Password);
|
||||
ctext.Parameters.AddWithValue("isadmin", record.IsAdmin);
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<Token>("tokenrecords");
|
||||
tokenrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in tokenrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.tokenrecords (id, emailaddress, body) VALUES(@id, @emailaddress, @body); SELECT setval('app.tokenrecords_id_seq', (SELECT MAX(id) from app.tokenrecords));";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("emailaddress", record.EmailAddress);
|
||||
ctext.Parameters.AddWithValue("body", record.Body);
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserConfigData>("userconfigrecords");
|
||||
userconfigrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in userconfigrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.userconfigrecords (id, data) VALUES(@id, CAST(@data AS jsonb))";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<UserAccess>("useraccessrecords");
|
||||
useraccessrecords = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in useraccessrecords)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.useraccessrecords (userId, vehicleId) VALUES(@userId, @vehicleId)";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", record.Id.UserId);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.Id.VehicleId);
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region "Part5"
|
||||
using (var db = new LiteDatabase(fullFileName))
|
||||
{
|
||||
var table = db.GetCollection<RecordExtraField>("extrafields");
|
||||
extrafields = table.FindAll().ToList();
|
||||
};
|
||||
foreach (var record in extrafields)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.extrafields (id, data) VALUES(@id, CAST(@data AS jsonb))";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
return Json(new OperationResponse { Success = true, Message = "Data Imported Successfully" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace CarCareTracker.Controllers
|
||||
private readonly IReminderHelper _reminderHelper;
|
||||
private readonly IReportHelper _reportHelper;
|
||||
private readonly IUserLogic _userLogic;
|
||||
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
||||
|
||||
public VehicleController(ILogger<VehicleController> logger,
|
||||
IFileHelper fileHelper,
|
||||
@@ -54,6 +55,7 @@ namespace CarCareTracker.Controllers
|
||||
IPlanRecordDataAccess planRecordDataAccess,
|
||||
IPlanRecordTemplateDataAccess planRecordTemplateDataAccess,
|
||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||
IExtraFieldDataAccess extraFieldDataAccess,
|
||||
IUserLogic userLogic,
|
||||
IWebHostEnvironment webEnv,
|
||||
IConfigHelper config)
|
||||
@@ -75,6 +77,7 @@ namespace CarCareTracker.Controllers
|
||||
_planRecordDataAccess = planRecordDataAccess;
|
||||
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
|
||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||
_extraFieldDataAccess = extraFieldDataAccess;
|
||||
_userLogic = userLogic;
|
||||
_webEnv = webEnv;
|
||||
_config = config;
|
||||
@@ -161,7 +164,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
if (vehicleId == default && mode != ImportMode.SupplyRecord)
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
@@ -256,7 +259,8 @@ namespace CarCareTracker.Controllers
|
||||
PartNumber = x.PartNumber,
|
||||
PartQuantity = x.Quantity.ToString(),
|
||||
PartSupplier = x.PartSupplier,
|
||||
Notes = x.Notes
|
||||
Notes = x.Notes,
|
||||
Tags = string.Join(" ", x.Tags)
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
@@ -349,7 +353,11 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
|
||||
{
|
||||
if (vehicleId == default || string.IsNullOrWhiteSpace(fileName))
|
||||
if (vehicleId == default && mode != ImportMode.SupplyRecord)
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
return Json(false);
|
||||
}
|
||||
@@ -504,7 +512,8 @@ namespace CarCareTracker.Controllers
|
||||
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
|
||||
Description = importModel.Description,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Notes = importModel.Notes
|
||||
Notes = importModel.Notes,
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
};
|
||||
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
|
||||
}
|
||||
@@ -583,7 +592,7 @@ namespace CarCareTracker.Controllers
|
||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
var vehicleIsElectric = vehicleData.IsElectric;
|
||||
var vehicleUseHours = vehicleData.UseHours;
|
||||
return PartialView("_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() });
|
||||
return PartialView("_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields } });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetGasRecordForEditById(int gasRecordId)
|
||||
@@ -601,7 +610,8 @@ namespace CarCareTracker.Controllers
|
||||
IsFillToFull = result.IsFillToFull,
|
||||
MissedFuelUp = result.MissedFuelUp,
|
||||
Notes = result.Notes,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields)
|
||||
};
|
||||
var vehicleData = _dataAccess.GetVehicleById(convertedResult.VehicleId);
|
||||
var vehicleIsElectric = vehicleData.IsElectric;
|
||||
@@ -672,7 +682,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddServiceRecordPartialView()
|
||||
{
|
||||
return PartialView("_ServiceRecordModal", new ServiceRecordInput());
|
||||
return PartialView("_ServiceRecordModal", new ServiceRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
|
||||
@@ -689,7 +699,8 @@ namespace CarCareTracker.Controllers
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_ServiceRecordModal", convertedResult);
|
||||
}
|
||||
@@ -742,7 +753,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddCollisionRecordPartialView()
|
||||
{
|
||||
return PartialView("_CollisionRecordModal", new CollisionRecordInput());
|
||||
return PartialView("_CollisionRecordModal", new CollisionRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
|
||||
@@ -759,7 +770,8 @@ namespace CarCareTracker.Controllers
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_CollisionRecordModal", convertedResult);
|
||||
}
|
||||
@@ -795,7 +807,14 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
foreach(TaxRecord recurringFee in recurringFees)
|
||||
{
|
||||
var newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
|
||||
var newDate = new DateTime();
|
||||
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other)
|
||||
{
|
||||
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
|
||||
} else
|
||||
{
|
||||
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
|
||||
}
|
||||
if (DateTime.Now > newDate){
|
||||
recurringFee.IsRecurring = false;
|
||||
var newRecurringFee = new TaxRecord()
|
||||
@@ -807,8 +826,10 @@ namespace CarCareTracker.Controllers
|
||||
IsRecurring = true,
|
||||
Notes = recurringFee.Notes,
|
||||
RecurringInterval = recurringFee.RecurringInterval,
|
||||
CustomMonthInterval = recurringFee.CustomMonthInterval,
|
||||
Files = recurringFee.Files,
|
||||
Tags = recurringFee.Tags
|
||||
Tags = recurringFee.Tags,
|
||||
ExtraFields = recurringFee.ExtraFields
|
||||
};
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
|
||||
@@ -827,7 +848,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddTaxRecordPartialView()
|
||||
{
|
||||
return PartialView("_TaxRecordModal", new TaxRecordInput());
|
||||
return PartialView("_TaxRecordModal", new TaxRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetTaxRecordForEditById(int taxRecordId)
|
||||
@@ -844,8 +865,10 @@ namespace CarCareTracker.Controllers
|
||||
VehicleId = result.VehicleId,
|
||||
IsRecurring = result.IsRecurring,
|
||||
RecurringInterval = result.RecurringInterval,
|
||||
CustomMonthInterval = result.CustomMonthInterval,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_TaxRecordModal", convertedResult);
|
||||
}
|
||||
@@ -1348,7 +1371,8 @@ namespace CarCareTracker.Controllers
|
||||
IsRecurring = result.IsRecurring,
|
||||
ReminderMileageInterval = result.ReminderMileageInterval,
|
||||
ReminderMonthInterval = result.ReminderMonthInterval,
|
||||
CustomMileageInterval = result.CustomMileageInterval
|
||||
CustomMileageInterval = result.CustomMileageInterval,
|
||||
CustomMonthInterval = result.CustomMonthInterval
|
||||
};
|
||||
return PartialView("_ReminderRecordModal", convertedResult);
|
||||
}
|
||||
@@ -1401,7 +1425,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddUpgradeRecordPartialView()
|
||||
{
|
||||
return PartialView("_UpgradeRecordModal", new UpgradeRecordInput());
|
||||
return PartialView("_UpgradeRecordModal", new UpgradeRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
||||
@@ -1418,7 +1442,8 @@ namespace CarCareTracker.Controllers
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_UpgradeRecordModal", convertedResult);
|
||||
}
|
||||
@@ -1479,7 +1504,11 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
//get supply record.
|
||||
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||
if (supply.Quantity > supplyData.Quantity)
|
||||
if (supplyData == null)
|
||||
{
|
||||
result.Add("Missing Supplies, Please Delete This Template and Recreate It.");
|
||||
}
|
||||
else if (supply.Quantity > supplyData.Quantity)
|
||||
{
|
||||
result.Add($"Insufficient Quantity for {supplyData.Description}, need: {supply.Quantity}, available: {supplyData.Quantity}");
|
||||
}
|
||||
@@ -1522,6 +1551,10 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult GetSupplyRecordsForRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
|
||||
if (_config.GetServerEnableShopSupplies())
|
||||
{
|
||||
result.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
|
||||
}
|
||||
result.RemoveAll(x => x.Quantity <= 0);
|
||||
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||
if (_useDescending)
|
||||
@@ -1545,7 +1578,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddSupplyRecordPartialView()
|
||||
{
|
||||
return PartialView("_SupplyRecordModal", new SupplyRecordInput());
|
||||
return PartialView("_SupplyRecordModal", new SupplyRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
|
||||
@@ -1563,7 +1596,9 @@ namespace CarCareTracker.Controllers
|
||||
PartSupplier = result.PartSupplier,
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files
|
||||
Files = result.Files,
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_SupplyRecordModal", convertedResult);
|
||||
}
|
||||
@@ -1657,7 +1692,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddPlanRecordPartialView()
|
||||
{
|
||||
return PartialView("_PlanRecordModal", new PlanRecordInput());
|
||||
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
|
||||
@@ -1675,7 +1710,8 @@ namespace CarCareTracker.Controllers
|
||||
Date = DateTime.Now,
|
||||
VehicleId = existingRecord.VehicleId,
|
||||
Mileage = odometer,
|
||||
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}"
|
||||
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
|
||||
ExtraFields = existingRecord.ExtraFields
|
||||
});
|
||||
}
|
||||
//convert plan record to service/upgrade/repair record.
|
||||
@@ -1689,7 +1725,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = existingRecord.Description,
|
||||
Cost = existingRecord.Cost,
|
||||
Notes = existingRecord.Notes,
|
||||
Files = existingRecord.Files
|
||||
Files = existingRecord.Files,
|
||||
ExtraFields = existingRecord.ExtraFields
|
||||
};
|
||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
|
||||
}
|
||||
@@ -1703,7 +1740,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = existingRecord.Description,
|
||||
Cost = existingRecord.Cost,
|
||||
Notes = existingRecord.Notes,
|
||||
Files = existingRecord.Files
|
||||
Files = existingRecord.Files,
|
||||
ExtraFields = existingRecord.ExtraFields
|
||||
};
|
||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
|
||||
}
|
||||
@@ -1717,7 +1755,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = existingRecord.Description,
|
||||
Cost = existingRecord.Cost,
|
||||
Notes = existingRecord.Notes,
|
||||
Files = existingRecord.Files
|
||||
Files = existingRecord.Files,
|
||||
ExtraFields = existingRecord.ExtraFields
|
||||
};
|
||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
|
||||
}
|
||||
@@ -1741,7 +1780,8 @@ namespace CarCareTracker.Controllers
|
||||
Cost = result.Cost,
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files
|
||||
Files = result.Files,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_PlanRecordModal", convertedResult);
|
||||
}
|
||||
@@ -1780,7 +1820,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpGet]
|
||||
public IActionResult GetAddOdometerRecordPartialView()
|
||||
{
|
||||
return PartialView("_OdometerRecordModal", new OdometerRecordInput());
|
||||
return PartialView("_OdometerRecordModal", new OdometerRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
||||
@@ -1795,7 +1835,8 @@ namespace CarCareTracker.Controllers
|
||||
Notes = result.Notes,
|
||||
VehicleId = result.VehicleId,
|
||||
Files = result.Files,
|
||||
Tags = result.Tags
|
||||
Tags = result.Tags,
|
||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields)
|
||||
};
|
||||
return PartialView("_OdometerRecordModal", convertedResult);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
public enum ReminderMonthInterval
|
||||
{
|
||||
Other = 0,
|
||||
OneMonth = 1,
|
||||
ThreeMonths = 3,
|
||||
SixMonths = 6,
|
||||
|
||||
30
External/Implementations/Litedb/ExtraFieldDataAccess.cs
vendored
Normal file
30
External/Implementations/Litedb/ExtraFieldDataAccess.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class ExtraFieldDataAccess: IExtraFieldDataAccess
|
||||
{
|
||||
private static string dbName = StaticHelper.DbName;
|
||||
private static string tableName = "extrafields";
|
||||
public RecordExtraField GetExtraFieldsById(int importMode)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<RecordExtraField>(tableName);
|
||||
return table.FindById(importMode) ?? new RecordExtraField();
|
||||
};
|
||||
}
|
||||
public bool SaveExtraFields(RecordExtraField record)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<RecordExtraField>(tableName);
|
||||
table.Upsert(record);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
160
External/Implementations/Postgres/CollisionRecordDataAccess.cs
vendored
Normal file
160
External/Implementations/Postgres/CollisionRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGCollisionRecordDataAccess : ICollisionRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGCollisionRecordDataAccess> _logger;
|
||||
private static string tableName = "collisionrecords";
|
||||
public PGCollisionRecordDataAccess(IConfiguration config, ILogger<PGCollisionRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<CollisionRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
CollisionRecord collisionRecord = JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string);
|
||||
results.Add(collisionRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<CollisionRecord>();
|
||||
}
|
||||
}
|
||||
public CollisionRecord GetCollisionRecordById(int collisionRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new CollisionRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", collisionRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
CollisionRecord collisionRecord = JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string);
|
||||
result = collisionRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new CollisionRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteCollisionRecordById(int collisionRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", collisionRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveCollisionRecordToVehicle(CollisionRecord collisionRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (collisionRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", collisionRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
collisionRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (collisionRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(collisionRecord);
|
||||
ctextU.Parameters.AddWithValue("id", collisionRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return collisionRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(collisionRecord);
|
||||
ctext.Parameters.AddWithValue("id", collisionRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllCollisionRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
External/Implementations/Postgres/ExtraFieldDataAccess.cs
vendored
Normal file
80
External/Implementations/Postgres/ExtraFieldDataAccess.cs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGExtraFieldDataAccess : IExtraFieldDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGExtraFieldDataAccess> _logger;
|
||||
private static string tableName = "extrafields";
|
||||
public PGExtraFieldDataAccess(IConfiguration config, ILogger<PGExtraFieldDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public RecordExtraField GetExtraFieldsById(int importMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var results = new RecordExtraField();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", importMode);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
RecordExtraField result = JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string);
|
||||
results = result;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new RecordExtraField();
|
||||
}
|
||||
}
|
||||
public bool SaveExtraFields(RecordExtraField record)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingRecord = GetExtraFieldsById(record.Id);
|
||||
string cmd = $"INSERT INTO app.{tableName} (id, data) VALUES(@id, CAST(@data AS jsonb))";
|
||||
if (existingRecord.ExtraFields != null)
|
||||
{
|
||||
cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
}
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/GasRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/GasRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGGasRecordDataAccess: IGasRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGGasRecordDataAccess> _logger;
|
||||
private static string tableName = "gasrecords";
|
||||
public PGGasRecordDataAccess(IConfiguration config, ILogger<PGGasRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<GasRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
GasRecord gasRecord = JsonSerializer.Deserialize<GasRecord>(reader["data"] as string);
|
||||
results.Add(gasRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<GasRecord>();
|
||||
}
|
||||
}
|
||||
public GasRecord GetGasRecordById(int gasRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new GasRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", gasRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
GasRecord gasRecord = JsonSerializer.Deserialize<GasRecord>(reader["data"] as string);
|
||||
result = gasRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new GasRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteGasRecordById(int gasRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", gasRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveGasRecordToVehicle(GasRecord gasRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (gasRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", gasRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
gasRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (gasRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(gasRecord);
|
||||
ctextU.Parameters.AddWithValue("id", gasRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return gasRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(gasRecord);
|
||||
ctext.Parameters.AddWithValue("id", gasRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllGasRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
External/Implementations/Postgres/NoteDataAccess.cs
vendored
Normal file
153
External/Implementations/Postgres/NoteDataAccess.cs
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGNoteDataAccess: INoteDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGNoteDataAccess> _logger;
|
||||
private static string tableName = "notes";
|
||||
public PGNoteDataAccess(IConfiguration config, ILogger<PGNoteDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<Note> GetNotesByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<Note>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Note note = JsonSerializer.Deserialize<Note>(reader["data"] as string);
|
||||
results.Add(note);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<Note>();
|
||||
}
|
||||
}
|
||||
public Note GetNoteById(int noteId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new Note();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", noteId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Note note = JsonSerializer.Deserialize<Note>(reader["data"] as string);
|
||||
result = note;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new Note();
|
||||
}
|
||||
}
|
||||
public bool SaveNoteToVehicle(Note note)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (note.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", note.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
note.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (note.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(note);
|
||||
ctextU.Parameters.AddWithValue("id", note.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return note.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(note);
|
||||
ctext.Parameters.AddWithValue("id", note.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteNoteById(int noteId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", noteId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllNotesByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/OdometerRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/OdometerRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGOdometerRecordDataAccess : IOdometerRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGOdometerRecordDataAccess> _logger;
|
||||
private static string tableName = "odometerrecords";
|
||||
public PGOdometerRecordDataAccess(IConfiguration config, ILogger<PGOdometerRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<OdometerRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
OdometerRecord odometerRecord = JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string);
|
||||
results.Add(odometerRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<OdometerRecord>();
|
||||
}
|
||||
}
|
||||
public OdometerRecord GetOdometerRecordById(int odometerRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new OdometerRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", odometerRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
OdometerRecord odometerRecord = JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string);
|
||||
result = odometerRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new OdometerRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteOdometerRecordById(int odometerRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", odometerRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (odometerRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", odometerRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
odometerRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (odometerRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(odometerRecord);
|
||||
ctextU.Parameters.AddWithValue("id", odometerRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return odometerRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(odometerRecord);
|
||||
ctext.Parameters.AddWithValue("id", odometerRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/PlanRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/PlanRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGPlanRecordDataAccess : IPlanRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGPlanRecordDataAccess> _logger;
|
||||
private static string tableName = "planrecords";
|
||||
public PGPlanRecordDataAccess(IConfiguration config, ILogger<PGPlanRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<PlanRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
PlanRecord planRecord = JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string);
|
||||
results.Add(planRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<PlanRecord>();
|
||||
}
|
||||
}
|
||||
public PlanRecord GetPlanRecordById(int planRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new PlanRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", planRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
PlanRecord planRecord = JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string);
|
||||
result = planRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new PlanRecord();
|
||||
}
|
||||
}
|
||||
public bool DeletePlanRecordById(int planRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", planRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SavePlanRecordToVehicle(PlanRecord planRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (planRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", planRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
planRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (planRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(planRecord);
|
||||
ctextU.Parameters.AddWithValue("id", planRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return planRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(planRecord);
|
||||
ctext.Parameters.AddWithValue("id", planRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGPlanRecordTemplateDataAccess : IPlanRecordTemplateDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGPlanRecordTemplateDataAccess> _logger;
|
||||
private static string tableName = "planrecordtemplates";
|
||||
public PGPlanRecordTemplateDataAccess(IConfiguration config, ILogger<PGPlanRecordTemplateDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<PlanRecordInput> GetPlanRecordTemplatesByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<PlanRecordInput>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
PlanRecordInput planRecord = JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string);
|
||||
results.Add(planRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<PlanRecordInput>();
|
||||
}
|
||||
}
|
||||
public PlanRecordInput GetPlanRecordTemplateById(int planRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new PlanRecordInput();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", planRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
PlanRecordInput planRecord = JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string);
|
||||
result = planRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new PlanRecordInput();
|
||||
}
|
||||
}
|
||||
public bool DeletePlanRecordTemplateById(int planRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", planRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SavePlanRecordTemplateToVehicle(PlanRecordInput planRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (planRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", planRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
planRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (planRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(planRecord);
|
||||
ctextU.Parameters.AddWithValue("id", planRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return planRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(planRecord);
|
||||
ctext.Parameters.AddWithValue("id", planRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllPlanRecordTemplatesByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/ReminderRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/ReminderRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGReminderRecordDataAccess : IReminderRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGReminderRecordDataAccess> _logger;
|
||||
private static string tableName = "reminderrecords";
|
||||
public PGReminderRecordDataAccess(IConfiguration config, ILogger<PGReminderRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<ReminderRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
ReminderRecord reminderRecord = JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string);
|
||||
results.Add(reminderRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<ReminderRecord>();
|
||||
}
|
||||
}
|
||||
public ReminderRecord GetReminderRecordById(int reminderRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new ReminderRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", reminderRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
ReminderRecord reminderRecord = JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string);
|
||||
result = reminderRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new ReminderRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteReminderRecordById(int reminderRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", reminderRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reminderRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", reminderRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
reminderRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (reminderRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(reminderRecord);
|
||||
ctextU.Parameters.AddWithValue("id", reminderRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return reminderRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(reminderRecord);
|
||||
ctext.Parameters.AddWithValue("id", reminderRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/ServiceRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/ServiceRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGServiceRecordDataAccess: IServiceRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGServiceRecordDataAccess> _logger;
|
||||
private static string tableName = "servicerecords";
|
||||
public PGServiceRecordDataAccess(IConfiguration config, ILogger<PGServiceRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<ServiceRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
ServiceRecord serviceRecord = JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string);
|
||||
results.Add(serviceRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<ServiceRecord>();
|
||||
}
|
||||
}
|
||||
public ServiceRecord GetServiceRecordById(int serviceRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new ServiceRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", serviceRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
ServiceRecord serviceRecord = JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string);
|
||||
result = serviceRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new ServiceRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteServiceRecordById(int serviceRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", serviceRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveServiceRecordToVehicle(ServiceRecord serviceRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (serviceRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", serviceRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
serviceRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (serviceRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(serviceRecord);
|
||||
ctextU.Parameters.AddWithValue("id", serviceRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return serviceRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(serviceRecord);
|
||||
ctext.Parameters.AddWithValue("id", serviceRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllServiceRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/SupplyRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/SupplyRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGSupplyRecordDataAccess : ISupplyRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGSupplyRecordDataAccess> _logger;
|
||||
private static string tableName = "supplyrecords";
|
||||
public PGSupplyRecordDataAccess(IConfiguration config, ILogger<PGSupplyRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<SupplyRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
SupplyRecord supplyRecord = JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string);
|
||||
results.Add(supplyRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<SupplyRecord>();
|
||||
}
|
||||
}
|
||||
public SupplyRecord GetSupplyRecordById(int supplyRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new SupplyRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", supplyRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
SupplyRecord supplyRecord = JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string);
|
||||
result = supplyRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new SupplyRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteSupplyRecordById(int supplyRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", supplyRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (supplyRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", supplyRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
supplyRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (supplyRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(supplyRecord);
|
||||
ctextU.Parameters.AddWithValue("id", supplyRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return supplyRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(supplyRecord);
|
||||
ctext.Parameters.AddWithValue("id", supplyRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/TaxRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/TaxRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGTaxRecordDataAccess : ITaxRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGTaxRecordDataAccess> _logger;
|
||||
private static string tableName = "taxrecords";
|
||||
public PGTaxRecordDataAccess(IConfiguration config, ILogger<PGTaxRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<TaxRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
TaxRecord taxRecord = JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string);
|
||||
results.Add(taxRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<TaxRecord>();
|
||||
}
|
||||
}
|
||||
public TaxRecord GetTaxRecordById(int taxRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new TaxRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", taxRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
TaxRecord taxRecord = JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string);
|
||||
result = taxRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new TaxRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteTaxRecordById(int taxRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", taxRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveTaxRecordToVehicle(TaxRecord taxRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (taxRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", taxRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
taxRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (taxRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(taxRecord);
|
||||
ctextU.Parameters.AddWithValue("id", taxRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return taxRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(taxRecord);
|
||||
ctext.Parameters.AddWithValue("id", taxRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllTaxRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
External/Implementations/Postgres/TokenRecordDataAccess.cs
vendored
Normal file
143
External/Implementations/Postgres/TokenRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGTokenRecordDataAccess : ITokenRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGTokenRecordDataAccess> _logger;
|
||||
private static string tableName = "tokenrecords";
|
||||
public PGTokenRecordDataAccess(IConfiguration config, ILogger<PGTokenRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<Token> GetTokens()
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, emailaddress, body FROM app.{tableName}";
|
||||
var results = new List<Token>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Token result = new Token();
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Body = reader["body"].ToString();
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<Token>();
|
||||
}
|
||||
}
|
||||
public Token GetTokenRecordByBody(string tokenBody)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, emailaddress, body FROM app.{tableName} WHERE body = @body";
|
||||
var result = new Token();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("body", tokenBody);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Body = reader["body"].ToString();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new Token();
|
||||
}
|
||||
}
|
||||
public Token GetTokenRecordByEmailAddress(string emailAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, emailaddress, body FROM app.{tableName} WHERE emailaddress = @emailaddress";
|
||||
var result = new Token();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("emailaddress", emailAddress);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Body = reader["body"].ToString();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new Token();
|
||||
}
|
||||
}
|
||||
public bool CreateNewToken(Token token)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (emailaddress, body) VALUES(@emailaddress, @body) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("emailaddress", token.EmailAddress);
|
||||
ctext.Parameters.AddWithValue("body", token.Body);
|
||||
token.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
return token.Id != default;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteToken(int tokenId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", tokenId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
External/Implementations/Postgres/UpgradeRecordDataAccess.cs
vendored
Normal file
159
External/Implementations/Postgres/UpgradeRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGUpgradeRecordDataAccess : IUpgradeRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGUpgradeRecordDataAccess> _logger;
|
||||
private static string tableName = "upgraderecords";
|
||||
public PGUpgradeRecordDataAccess(IConfiguration config, ILogger<PGUpgradeRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<UpgradeRecord>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UpgradeRecord upgradeRecord = JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string);
|
||||
results.Add(upgradeRecord);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<UpgradeRecord>();
|
||||
}
|
||||
}
|
||||
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
var result = new UpgradeRecord();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", upgradeRecordId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UpgradeRecord upgradeRecord = JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string);
|
||||
result = upgradeRecord;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UpgradeRecord();
|
||||
}
|
||||
}
|
||||
public bool DeleteUpgradeRecordById(int upgradeRecordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", upgradeRecordId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (upgradeRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", upgradeRecord.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
upgradeRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (upgradeRecord.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(upgradeRecord);
|
||||
ctextU.Parameters.AddWithValue("id", upgradeRecord.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return upgradeRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(upgradeRecord);
|
||||
ctext.Parameters.AddWithValue("id", upgradeRecord.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
External/Implementations/Postgres/UserAccessDataAcces.cs
vendored
Normal file
211
External/Implementations/Postgres/UserAccessDataAcces.cs
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGUserAccessDataAccess : IUserAccessDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGUserAccessDataAccess> _logger;
|
||||
private static string tableName = "useraccessrecords";
|
||||
public PGUserAccessDataAccess(IConfiguration config, ILogger<PGUserAccessDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a list of vehicles user have access to.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public List<UserAccess> GetUserAccessByUserId(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT userId, vehicleId FROM app.{tableName} WHERE userId = @userId";
|
||||
var results = new List<UserAccess>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserAccess result = new UserAccess()
|
||||
{
|
||||
Id = new UserVehicle
|
||||
{
|
||||
UserId = int.Parse(reader["userId"].ToString()),
|
||||
VehicleId = int.Parse(reader["vehicleId"].ToString())
|
||||
}
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<UserAccess>();
|
||||
}
|
||||
}
|
||||
public UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT userId, vehicleId FROM app.{tableName} WHERE userId = @userId AND vehicleId = @vehicleId";
|
||||
UserAccess result = null;
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userId);
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result = new UserAccess()
|
||||
{
|
||||
Id = new UserVehicle
|
||||
{
|
||||
UserId = int.Parse(reader["userId"].ToString()),
|
||||
VehicleId = int.Parse(reader["vehicleId"].ToString())
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UserAccess();
|
||||
}
|
||||
}
|
||||
public List<UserAccess> GetUserAccessByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT userId, vehicleId FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
var results = new List<UserAccess>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserAccess result = new UserAccess()
|
||||
{
|
||||
Id = new UserVehicle
|
||||
{
|
||||
UserId = int.Parse(reader["userId"].ToString()),
|
||||
VehicleId = int.Parse(reader["vehicleId"].ToString())
|
||||
}
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<UserAccess>();
|
||||
}
|
||||
}
|
||||
public bool SaveUserAccess(UserAccess userAccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (userId, vehicleId) VALUES(@userId, @vehicleId)";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userAccess.Id.UserId);
|
||||
ctext.Parameters.AddWithValue("vehicleId", userAccess.Id.VehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteUserAccess(int userId, int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE userId = @userId AND vehicleId = @vehicleId";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userId);
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Delete all access records when a vehicle is deleted.
|
||||
/// </summary>
|
||||
/// <param name="vehicleId"></param>
|
||||
/// <returns></returns>
|
||||
public bool DeleteAllAccessRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @vehicleId";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Delee all access records when a user is deleted.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public bool DeleteAllAccessRecordsByUserId(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE userId = @userId";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
External/Implementations/Postgres/UserConfigDataAccess.cs
vendored
Normal file
107
External/Implementations/Postgres/UserConfigDataAccess.cs
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGUserConfigDataAccess: IUserConfigDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGUserConfigDataAccess> _logger;
|
||||
private static string tableName = "userconfigrecords";
|
||||
public PGUserConfigDataAccess(IConfiguration config, ILogger<PGUserConfigDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public UserConfigData GetUserConfig(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
|
||||
UserConfigData result = null;
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserConfigData userConfig = JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string);
|
||||
result = userConfig;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UserConfigData();
|
||||
}
|
||||
}
|
||||
public bool SaveUserConfig(UserConfigData userConfigData)
|
||||
{
|
||||
var existingRecord = GetUserConfig(userConfigData.Id);
|
||||
try
|
||||
{
|
||||
if (existingRecord == null)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (id, data) VALUES(@id, CAST(@data AS jsonb))";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(userConfigData);
|
||||
ctext.Parameters.AddWithValue("id", userConfigData.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(userConfigData);
|
||||
ctext.Parameters.AddWithValue("id", userConfigData.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteUserConfig(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
194
External/Implementations/Postgres/UserRecordDataAccess.cs
vendored
Normal file
194
External/Implementations/Postgres/UserRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGUserRecordDataAccess : IUserRecordDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGUserRecordDataAccess> _logger;
|
||||
private static string tableName = "userrecords";
|
||||
public PGUserRecordDataAccess(IConfiguration config, ILogger<PGUserRecordDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public List<UserData> GetUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, username, emailaddress, password, isadmin FROM app.{tableName}";
|
||||
var results = new List<UserData>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
UserData result = new UserData();
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.UserName = reader["username"].ToString();
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Password = reader["password"].ToString();
|
||||
result.IsAdmin = bool.Parse(reader["isadmin"].ToString());
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<UserData>();
|
||||
}
|
||||
}
|
||||
public UserData GetUserRecordByUserName(string userName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, username, emailaddress, password, isadmin FROM app.{tableName} WHERE username = @username";
|
||||
var result = new UserData();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("username", userName);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.UserName = reader["username"].ToString();
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Password = reader["password"].ToString();
|
||||
result.IsAdmin = bool.Parse(reader["isadmin"].ToString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UserData();
|
||||
}
|
||||
}
|
||||
public UserData GetUserRecordByEmailAddress(string emailAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, username, emailaddress, password, isadmin FROM app.{tableName} WHERE emailaddress = @emailaddress";
|
||||
var result = new UserData();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("emailaddress", emailAddress);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.UserName = reader["username"].ToString();
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Password = reader["password"].ToString();
|
||||
result.IsAdmin = bool.Parse(reader["isadmin"].ToString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UserData();
|
||||
}
|
||||
}
|
||||
public UserData GetUserRecordById(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, username, emailaddress, password, isadmin FROM app.{tableName} WHERE id = @id";
|
||||
var result = new UserData();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Id = int.Parse(reader["id"].ToString());
|
||||
result.UserName = reader["username"].ToString();
|
||||
result.EmailAddress = reader["emailaddress"].ToString();
|
||||
result.Password = reader["password"].ToString();
|
||||
result.IsAdmin = bool.Parse(reader["isadmin"].ToString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new UserData();
|
||||
}
|
||||
}
|
||||
public bool SaveUserRecord(UserData userRecord)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (userRecord.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (username, emailaddress, password, isadmin) VALUES(@username, @emailaddress, @password, @isadmin) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("username", userRecord.UserName);
|
||||
ctext.Parameters.AddWithValue("emailaddress", userRecord.EmailAddress);
|
||||
ctext.Parameters.AddWithValue("password", userRecord.Password);
|
||||
ctext.Parameters.AddWithValue("isadmin", userRecord.IsAdmin);
|
||||
userRecord.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
return userRecord.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET username = @username, emailaddress = @emailaddress, password = @password, isadmin = @isadmin WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userRecord.Id);
|
||||
ctext.Parameters.AddWithValue("username", userRecord.UserName);
|
||||
ctext.Parameters.AddWithValue("emailaddress", userRecord.EmailAddress);
|
||||
ctext.Parameters.AddWithValue("password", userRecord.Password);
|
||||
ctext.Parameters.AddWithValue("isadmin", userRecord.IsAdmin);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteUserRecord(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
External/Implementations/Postgres/VehicleDataAccess.cs
vendored
Normal file
138
External/Implementations/Postgres/VehicleDataAccess.cs
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PGVehicleDataAccess: IVehicleDataAccess
|
||||
{
|
||||
private NpgsqlDataSource pgDataSource;
|
||||
private readonly ILogger<PGVehicleDataAccess> _logger;
|
||||
private static string tableName = "vehicles";
|
||||
public PGVehicleDataAccess(IConfiguration config, ILogger<PGVehicleDataAccess> logger)
|
||||
{
|
||||
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
|
||||
_logger = logger;
|
||||
try
|
||||
{
|
||||
//create table if not exist.
|
||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)";
|
||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||
{
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
public bool SaveVehicle(Vehicle vehicle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(vehicle.ImageLocation))
|
||||
{
|
||||
vehicle.ImageLocation = "/defaults/noimage.png";
|
||||
}
|
||||
if (vehicle.Id == default)
|
||||
{
|
||||
string cmd = $"INSERT INTO app.{tableName} (data) VALUES(CAST(@data AS jsonb)) RETURNING id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("data", "{}");
|
||||
vehicle.Id = Convert.ToInt32(ctext.ExecuteScalar());
|
||||
//update json data
|
||||
if (vehicle.Id != default)
|
||||
{
|
||||
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctextU = pgDataSource.CreateCommand(cmdU))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(vehicle);
|
||||
ctextU.Parameters.AddWithValue("id", vehicle.Id);
|
||||
ctextU.Parameters.AddWithValue("data", serializedData);
|
||||
return ctextU.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
return vehicle.Id != default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(vehicle);
|
||||
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
||||
ctext.Parameters.AddWithValue("data", serializedData);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool DeleteVehicle(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public List<Vehicle> GetVehicles()
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, data FROM app.{tableName} ORDER BY id ASC";
|
||||
var results = new List<Vehicle>();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Vehicle vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
results.Add(vehicle);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new List<Vehicle>();
|
||||
}
|
||||
}
|
||||
public Vehicle GetVehicleById(int vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cmd = $"SELECT id, data FROM app.{tableName} WHERE id = @id";
|
||||
Vehicle vehicle = new Vehicle();
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
}
|
||||
}
|
||||
return vehicle;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return new Vehicle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
External/Interfaces/IExtraFieldDataAccess.cs
vendored
Normal file
10
External/Interfaces/IExtraFieldDataAccess.cs
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
using CarCareTracker.Models;
|
||||
|
||||
namespace CarCareTracker.External.Interfaces
|
||||
{
|
||||
public interface IExtraFieldDataAccess
|
||||
{
|
||||
public RecordExtraField GetExtraFieldsById(int importMode);
|
||||
public bool SaveExtraFields(RecordExtraField record);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using CarCareTracker.Logic;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Logic;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
@@ -9,18 +10,36 @@ namespace CarCareTracker.Filter
|
||||
public class CollaboratorFilter: ActionFilterAttribute
|
||||
{
|
||||
private readonly IUserLogic _userLogic;
|
||||
public CollaboratorFilter(IUserLogic userLogic) {
|
||||
private readonly IConfigHelper _config;
|
||||
public CollaboratorFilter(IUserLogic userLogic, IConfigHelper config) {
|
||||
_userLogic = userLogic;
|
||||
_config = config;
|
||||
}
|
||||
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))
|
||||
if (vehicleId != default)
|
||||
{
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
||||
{
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
} else
|
||||
{
|
||||
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
|
||||
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
|
||||
{
|
||||
//user trying to access shop supplies but shop supplies is not enabled by root user.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
|
||||
{
|
||||
//user trying to access any other endpoints using 0 as vehicle id.
|
||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace CarCareTracker.Helper
|
||||
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
||||
string GetLogoUrl();
|
||||
string GetServerLanguage();
|
||||
bool GetServerEnableShopSupplies();
|
||||
string GetServerPostgresConnection();
|
||||
public bool DeleteUserConfig(int userId);
|
||||
}
|
||||
public class ConfigHelper : IConfigHelper
|
||||
@@ -40,6 +42,20 @@ namespace CarCareTracker.Helper
|
||||
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
return serverLanguage;
|
||||
}
|
||||
public string GetServerPostgresConnection()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]))
|
||||
{
|
||||
return _config["POSTGRES_CONNECTION"];
|
||||
} else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
public bool GetServerEnableShopSupplies()
|
||||
{
|
||||
return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false");
|
||||
}
|
||||
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
|
||||
{
|
||||
var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
@@ -117,6 +133,7 @@ namespace CarCareTracker.Helper
|
||||
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
|
||||
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
|
||||
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
|
||||
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
||||
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(),
|
||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
|
||||
};
|
||||
|
||||
@@ -13,7 +13,14 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
if (existingReminder.Metric == ReminderMetric.Both)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
if (existingReminder.ReminderMonthInterval != ReminderMonthInterval.Other)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
} else
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
|
||||
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
||||
{
|
||||
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
|
||||
@@ -35,7 +42,14 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
else if (existingReminder.Metric == ReminderMetric.Date)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
if (existingReminder.ReminderMonthInterval != ReminderMonthInterval.Other)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths(existingReminder.CustomMonthInterval);
|
||||
}
|
||||
}
|
||||
return existingReminder;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using CarCareTracker.Models;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -128,7 +129,8 @@ namespace CarCareTracker.Helper
|
||||
Mileage = input.Mileage,
|
||||
Files = input.Files,
|
||||
Notes = input.Notes,
|
||||
Tags = input.Tags
|
||||
Tags = input.Tags,
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
}
|
||||
public static CollisionRecord GenericToRepairRecord(GenericRecord input)
|
||||
@@ -142,7 +144,8 @@ namespace CarCareTracker.Helper
|
||||
Mileage = input.Mileage,
|
||||
Files = input.Files,
|
||||
Notes = input.Notes,
|
||||
Tags = input.Tags
|
||||
Tags = input.Tags,
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
}
|
||||
public static UpgradeRecord GenericToUpgradeRecord(GenericRecord input)
|
||||
@@ -156,10 +159,35 @@ namespace CarCareTracker.Helper
|
||||
Mileage = input.Mileage,
|
||||
Files = input.Files,
|
||||
Notes = input.Notes,
|
||||
Tags = input.Tags
|
||||
Tags = input.Tags,
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
}
|
||||
|
||||
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
|
||||
{
|
||||
if (!templateExtraFields.Any()) {
|
||||
return new List<ExtraField>();
|
||||
}
|
||||
if (!recordExtraFields.Any())
|
||||
{
|
||||
return templateExtraFields;
|
||||
}
|
||||
var fieldNames = templateExtraFields.Select(x => x.Name);
|
||||
//remove fields that are no longer present in template.
|
||||
recordExtraFields.RemoveAll(x => !fieldNames.Contains(x.Name));
|
||||
if (!recordExtraFields.Any())
|
||||
{
|
||||
return templateExtraFields;
|
||||
}
|
||||
//append the fields.
|
||||
foreach (ExtraField extraField in recordExtraFields)
|
||||
{
|
||||
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
|
||||
}
|
||||
return recordExtraFields;
|
||||
}
|
||||
|
||||
public static string GetFuelEconomyUnit(bool useKwh, bool useHours, bool useMPG, bool useUKMPG)
|
||||
{
|
||||
string fuelEconomyUnit;
|
||||
|
||||
@@ -48,6 +48,12 @@ namespace CarCareTracker.Logic
|
||||
if (existingUser.Id != default)
|
||||
{
|
||||
//user exists.
|
||||
//check if user is already a collaborator
|
||||
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(existingUser.Id, vehicleId);
|
||||
if (userAccess != null)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "User is already a collaborator" };
|
||||
}
|
||||
var result = AddUserAccessToVehicle(existingUser.Id, vehicleId);
|
||||
if (result)
|
||||
{
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace CarCareTracker.Middleware
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Response.Redirect("/Error/401");
|
||||
Response.Redirect("/Error/Unauthorized");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
|
||||
}
|
||||
}
|
||||
|
||||
9
Models/ExtraField.cs
Normal file
9
Models/ExtraField.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class ExtraField
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,6 @@
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public GasRecord ToGasRecord() { return new GasRecord {
|
||||
Id = Id,
|
||||
Cost = Cost,
|
||||
@@ -30,7 +31,8 @@
|
||||
IsFillToFull = IsFillToFull,
|
||||
MissedFuelUp = MissedFuelUp,
|
||||
Notes = Notes,
|
||||
Tags = Tags
|
||||
Tags = Tags,
|
||||
ExtraFields = ExtraFields
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set;} = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
public string Description { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceRecordExportModel
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
public string Notes { get; set; }
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
public PlanPriority Priority { get; set; }
|
||||
public PlanProgress Progress { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
public PlanPriority Priority { get; set; }
|
||||
public PlanProgress Progress { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public PlanRecord ToPlanRecord() { return new PlanRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
@@ -25,7 +26,8 @@
|
||||
ImportMode = ImportMode,
|
||||
Cost = Cost,
|
||||
Priority = Priority,
|
||||
Progress = Progress
|
||||
Progress = Progress,
|
||||
ExtraFields = ExtraFields
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
14
Models/RecordExtraField.cs
Normal file
14
Models/RecordExtraField.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class RecordExtraField
|
||||
{
|
||||
/// <summary>
|
||||
/// Corresponds to int value of ImportMode enum
|
||||
/// </summary>
|
||||
[BsonId(false)]
|
||||
public int Id { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
|
||||
@@ -10,20 +10,27 @@
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
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,
|
||||
IsRecurring = IsRecurring,
|
||||
ReminderMileageInterval = ReminderMileageInterval,
|
||||
ReminderMonthInterval = ReminderMonthInterval,
|
||||
CustomMileageInterval = CustomMileageInterval,
|
||||
Notes = Notes }; }
|
||||
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,
|
||||
IsRecurring = IsRecurring,
|
||||
ReminderMileageInterval = ReminderMileageInterval,
|
||||
ReminderMonthInterval = ReminderMonthInterval,
|
||||
CustomMileageInterval = CustomMileageInterval,
|
||||
CustomMonthInterval = CustomMonthInterval,
|
||||
Notes = Notes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,7 @@
|
||||
/// </summary>
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public SupplyRecord ToSupplyRecord() { return new SupplyRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
@@ -22,6 +24,9 @@
|
||||
Quantity = Quantity,
|
||||
Description = Description,
|
||||
Notes = Notes,
|
||||
Files = Files }; }
|
||||
Files = Files,
|
||||
Tags = Tags,
|
||||
ExtraFields = ExtraFields
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
||||
public int CustomMonthInterval { get; set; } = 0;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public TaxRecord ToTaxRecord() { return new TaxRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
@@ -21,8 +23,10 @@
|
||||
Notes = Notes,
|
||||
IsRecurring = IsRecurring,
|
||||
RecurringInterval = RecurringInterval,
|
||||
CustomMonthInterval = CustomMonthInterval,
|
||||
Files = Files,
|
||||
Tags = Tags
|
||||
Tags = Tags,
|
||||
ExtraFields = ExtraFields
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
public bool UseMarkDownOnSavedNotes { get; set; }
|
||||
public bool EnableAutoReminderRefresh { get; set; }
|
||||
public bool EnableAutoOdometerInsert { get; set; }
|
||||
public bool EnableShopSupplies { get; set; }
|
||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||
public string UserNameHash { get; set; }
|
||||
|
||||
61
Program.cs
61
Program.cs
@@ -12,22 +12,47 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddSingleton<IVehicleDataAccess, VehicleDataAccess>();
|
||||
builder.Services.AddSingleton<INoteDataAccess, NoteDataAccess>();
|
||||
builder.Services.AddSingleton<IServiceRecordDataAccess, ServiceRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IGasRecordDataAccess, GasRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
||||
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>();
|
||||
builder.Services.AddSingleton<ISupplyRecordDataAccess, SupplyRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordDataAccess, PlanRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordTemplateDataAccess, PlanRecordTemplateDataAccess>();
|
||||
builder.Services.AddSingleton<IOdometerRecordDataAccess, OdometerRecordDataAccess>();
|
||||
|
||||
//data access method
|
||||
if (!string.IsNullOrWhiteSpace(builder.Configuration["POSTGRES_CONNECTION"])){
|
||||
builder.Services.AddSingleton<IVehicleDataAccess, PGVehicleDataAccess>();
|
||||
builder.Services.AddSingleton<INoteDataAccess, PGNoteDataAccess>();
|
||||
builder.Services.AddSingleton<IServiceRecordDataAccess, PGServiceRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IGasRecordDataAccess, PGGasRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ICollisionRecordDataAccess, PGCollisionRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ITaxRecordDataAccess, PGTaxRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IReminderRecordDataAccess, PGReminderRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IUpgradeRecordDataAccess, PGUpgradeRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IOdometerRecordDataAccess, PGOdometerRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ISupplyRecordDataAccess, PGSupplyRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordDataAccess, PGPlanRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordTemplateDataAccess, PGPlanRecordTemplateDataAccess>();
|
||||
builder.Services.AddSingleton<IUserConfigDataAccess, PGUserConfigDataAccess>();
|
||||
builder.Services.AddSingleton<IUserRecordDataAccess, PGUserRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ITokenRecordDataAccess, PGTokenRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IUserAccessDataAccess, PGUserAccessDataAccess>();
|
||||
builder.Services.AddSingleton<IExtraFieldDataAccess, PGExtraFieldDataAccess>();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Services.AddSingleton<IVehicleDataAccess, VehicleDataAccess>();
|
||||
builder.Services.AddSingleton<INoteDataAccess, NoteDataAccess>();
|
||||
builder.Services.AddSingleton<IServiceRecordDataAccess, ServiceRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IGasRecordDataAccess, GasRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ITaxRecordDataAccess, TaxRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IReminderRecordDataAccess, ReminderRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IUpgradeRecordDataAccess, UpgradeRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IOdometerRecordDataAccess, OdometerRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ISupplyRecordDataAccess, SupplyRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordDataAccess, PlanRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordTemplateDataAccess, PlanRecordTemplateDataAccess>();
|
||||
builder.Services.AddSingleton<IUserConfigDataAccess, UserConfigDataAccess>();
|
||||
builder.Services.AddSingleton<IUserRecordDataAccess, UserRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
|
||||
builder.Services.AddSingleton<IExtraFieldDataAccess, ExtraFieldDataAccess>();
|
||||
}
|
||||
|
||||
//configure helpers
|
||||
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
||||
@@ -46,6 +71,10 @@ if (!Directory.Exists("data"))
|
||||
{
|
||||
Directory.CreateDirectory("data");
|
||||
}
|
||||
if (!Directory.Exists("config"))
|
||||
{
|
||||
Directory.CreateDirectory("config");
|
||||
}
|
||||
|
||||
//Additional JsonFile
|
||||
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5011"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:11362",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,23 @@
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/garage.js"></script>
|
||||
<script src="~/js/supplyrecord.js"></script>
|
||||
<script src="~/lib/drawdown/drawdown.js"></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 user-select-none @(Model == "garage" ? "active" : "")" ontouchstart="detectLongTouch(this)" ontouchend="detectTouchEndPremature(this)" 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>@translator.Translate(userLanguage,"Garage")</span></button>
|
||||
</li>
|
||||
@if(config.GetServerEnableShopSupplies())
|
||||
{
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</button>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</button>
|
||||
</li>
|
||||
<li class="nav-item" 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"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</span></button>
|
||||
</li>
|
||||
@@ -50,6 +61,15 @@
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" 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>@translator.Translate(userLanguage,"Garage")</button>
|
||||
</li>
|
||||
@if (config.GetServerEnableShopSupplies())
|
||||
{
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</button>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</button>
|
||||
</li>
|
||||
<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>@translator.Translate(userLanguage,"Settings")</button>
|
||||
</li>
|
||||
@@ -77,6 +97,10 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="supply-tab-pane" role="tabpanel" tabindex="0">
|
||||
</div>
|
||||
<div class="tab-pane fade" id="calendar-tab-pane" role="tabpanel" tabindex="0">
|
||||
</div>
|
||||
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,6 +112,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="bulkImportModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content" id="bulkImportModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
loadGarage();
|
||||
bindWindowResize();
|
||||
|
||||
18
Views/Home/_Calendar.cshtml
Normal file
18
Views/Home/_Calendar.cshtml
Normal file
@@ -0,0 +1,18 @@
|
||||
@model List<ReminderRecordViewModel>
|
||||
<script>
|
||||
var eventDates = [];
|
||||
var groupedDates = [];
|
||||
@foreach(ReminderRecordViewModel reminderRecord in Model)
|
||||
{
|
||||
@:eventDates.push({date: new Date(Date.parse(decodeHTMLEntities('@reminderRecord.Date.ToShortDateString()'))), description: decodeHTMLEntities('@reminderRecord.Description'), urgency: decodeHTMLEntities('@reminderRecord.Urgency.ToString()')});
|
||||
}
|
||||
</script>
|
||||
<div class="row vehicleDetailTabContainer">
|
||||
<div class="col-12 reminderCalendarView">
|
||||
<div class="reminderCalendarViewContent" style="height:60vh;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
initCalendar();
|
||||
</script>
|
||||
124
Views/Home/_ExtraFields.cshtml
Normal file
124
Views/Home/_ExtraFields.cshtml
Normal file
@@ -0,0 +1,124 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model RecordExtraField
|
||||
<script>
|
||||
var extraFields = [];
|
||||
</script>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(translator.Translate(userLanguage, "Manage Extra Fields"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideExtraFieldModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<select class="form-select" onchange="refreshExtraFieldModal()" id="extraFieldTab">
|
||||
<!option @(Model.Id == (int)ImportMode.ServiceRecord ? "selected" : "") value="@((int)ImportMode.ServiceRecord)">@translator.Translate(userLanguage, "Service Record")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.RepairRecord ? "selected" : "") value="@((int)ImportMode.RepairRecord)">@translator.Translate(userLanguage, "Repairs")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.UpgradeRecord ? "selected" : "") value="@((int)ImportMode.UpgradeRecord)">@translator.Translate(userLanguage, "Upgrades")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.GasRecord ? "selected" : "") value="@((int)ImportMode.GasRecord)">@translator.Translate(userLanguage, "Fuel")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.TaxRecord ? "selected" : "") value="@((int)ImportMode.TaxRecord)">@translator.Translate(userLanguage, "Tax")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.SupplyRecord ? "selected" : "") value="@((int)ImportMode.SupplyRecord)">@translator.Translate(userLanguage, "Supplies")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.PlanRecord ? "selected" : "") value="@((int)ImportMode.PlanRecord)">@translator.Translate(userLanguage, "Planner")</!option>
|
||||
<!option @(Model.Id == (int)ImportMode.OdometerRecord ? "selected" : "") value="@((int)ImportMode.OdometerRecord)">@translator.Translate(userLanguage, "Odometer")</!option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-8">@translator.Translate(userLanguage, "Name")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Required")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Model.ExtraFields != null)
|
||||
{
|
||||
@foreach (ExtraField extraField in Model.ExtraFields)
|
||||
{
|
||||
<script>
|
||||
extraFields.push({ name: decodeHTMLEntities('@extraField.Name'), isRequired: @extraField.IsRequired.ToString().ToLower()});
|
||||
</script>
|
||||
<tr class="d-flex">
|
||||
<td class="col-8">@extraField.Name</td>
|
||||
<td class="col-2"><input class="form-check-input" type="checkbox" onchange="updateExtraFieldIsRequired(decodeHTMLEntities('@extraField.Name'), this)" value="" @(extraField.IsRequired ? "checked" : "") /></td>
|
||||
<td class="col-2"><button type="button" onclick="deleteExtraField(decodeHTMLEntities('@extraField.Name'))" class="btn btn-danger"><i class="bi bi-trash"></i></button></td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="hideExtraFieldModal()">@translator.Translate(userLanguage, "Close")</button>
|
||||
<button type="button" class="btn btn-primary" onclick="addExtraField()">@translator.Translate(userLanguage, "Add New Field")</button>
|
||||
</div>
|
||||
<script>
|
||||
var importMode = @Model.Id;
|
||||
function addExtraField() {
|
||||
Swal.fire({
|
||||
title: 'Field Name',
|
||||
html: `
|
||||
<input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name">
|
||||
`,
|
||||
confirmButtonText: 'Add',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
const extraFieldName = $("#inputExtraFieldName").val();
|
||||
if (!extraFieldName || extraFieldName.trim() == '') {
|
||||
Swal.showValidationMessage(`Field names cannot be blank`);
|
||||
} else if (extraFields.filter(x => x.name == extraFieldName).length > 0) {
|
||||
Swal.showValidationMessage(`Field names must be unique`);
|
||||
}
|
||||
return { extraFieldName }
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
extraFields.push({ name: result.value.extraFieldName, isRequired: false});
|
||||
updateExtraFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateExtraFieldIsRequired(fieldId, checkbox){
|
||||
var indexToEdit = extraFields.findIndex(x => x.name == fieldId);
|
||||
var extraFieldToEdit = extraFields[indexToEdit];
|
||||
extraFieldToEdit.isRequired = $(checkbox).is(":checked");
|
||||
updateExtraFields();
|
||||
}
|
||||
function deleteExtraField(fieldId) {
|
||||
extraFields = extraFields.filter(x => x.name != fieldId);
|
||||
updateExtraFields();
|
||||
}
|
||||
function refreshExtraFieldModal() {
|
||||
var selectedExtraFieldTab = $("#extraFieldTab").val();
|
||||
$.post(`/Home/GetExtraFieldsModal?importMode=${selectedExtraFieldTab}`, function (data) {
|
||||
if (data) {
|
||||
$("#extraFieldModalContent").html(data);
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateExtraFields() {
|
||||
$.post('/Home/UpdateExtraFields', { id: importMode, extraFields: extraFields }, function (data) {
|
||||
if (data) {
|
||||
$("#extraFieldModalContent").html(data);
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -53,6 +53,10 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
|
||||
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
|
||||
</div>
|
||||
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
||||
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="form-check form-switch">
|
||||
@@ -153,7 +157,7 @@
|
||||
<span class="lead">@translator.Translate(userLanguage, "Backups")</span>
|
||||
<div class="row">
|
||||
<div class="col-6 d-grid">
|
||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Make")</button>
|
||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Create")</button>
|
||||
</div>
|
||||
<div class="col-6 d-grid">
|
||||
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
|
||||
@@ -174,6 +178,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">@translator.Translate(userLanguage, "Manage Extra Fields")</span>
|
||||
<div class="row">
|
||||
<div class="col-12 d-grid">
|
||||
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Add/Remove Extra Fields")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -187,7 +201,7 @@
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<small class="text-body-secondary">Version 1.1.4</small>
|
||||
<small class="text-body-secondary">Version 1.1.6</small>
|
||||
</div>
|
||||
<p class="lead">
|
||||
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
|
||||
@@ -217,6 +231,7 @@
|
||||
<li class="list-group-item">Bootstrap</li>
|
||||
<li class="list-group-item">Bootstrap-DatePicker</li>
|
||||
<li class="list-group-item">LiteDB</li>
|
||||
<li class="list-group-item">Npgsql</li>
|
||||
<li class="list-group-item">SweetAlert2</li>
|
||||
<li class="list-group-item">CsvHelper</li>
|
||||
<li class="list-group-item">Chart.js</li>
|
||||
@@ -224,7 +239,22 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="extraFieldModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="extraFieldModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function showExtraFieldModal() {
|
||||
$.get(`/Home/GetExtraFieldsModal?importMode=0`, function (data) {
|
||||
$("#extraFieldModalContent").html(data);
|
||||
$("#extraFieldModal").modal('show');
|
||||
});
|
||||
}
|
||||
function hideExtraFieldModal() {
|
||||
$("#extraFieldModal").modal('hide');
|
||||
}
|
||||
function getCheckedTabs() {
|
||||
var visibleTabs = $("#visibleTabs :checked").map(function () {
|
||||
return this.value;
|
||||
@@ -256,6 +286,7 @@
|
||||
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
|
||||
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
||||
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
||||
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
|
||||
preferredGasUnit: $("#preferredGasUnit").val(),
|
||||
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
|
||||
userLanguage: $("#defaultLanguage").val(),
|
||||
@@ -277,13 +308,13 @@
|
||||
window.location.href = data;
|
||||
});
|
||||
}
|
||||
function openUploadLanguage(){
|
||||
function openUploadLanguage() {
|
||||
$("#inputLanguage").click();
|
||||
}
|
||||
function openRestoreBackup() {
|
||||
$("#inputBackup").click();
|
||||
}
|
||||
function uploadLanguage(event){
|
||||
function uploadLanguage(event) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", event.files[0]);
|
||||
sloader.show();
|
||||
@@ -302,7 +333,7 @@
|
||||
errorToast(response.message);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
error: function () {
|
||||
sloader.hide();
|
||||
errorToast("An error has occurred, please check the file size and try again later.");
|
||||
}
|
||||
@@ -345,9 +376,9 @@
|
||||
Swal.fire({
|
||||
title: 'Setup Credentials',
|
||||
html: `
|
||||
<input type="text" id="authUsername" class="swal2-input" placeholder="Username">
|
||||
<input type="password" id="authPassword" class="swal2-input" placeholder="Password">
|
||||
`,
|
||||
<input type="text" id="authUsername" class="swal2-input" placeholder="Username">
|
||||
<input type="password" id="authPassword" class="swal2-input" placeholder="Password">
|
||||
`,
|
||||
confirmButtonText: 'Setup',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
|
||||
88
Views/Migration/Index.cshtml
Normal file
88
Views/Migration/Index.cshtml
Normal file
@@ -0,0 +1,88 @@
|
||||
@using CarCareTracker.Helper
|
||||
@{
|
||||
ViewData["Title"] = "Admin";
|
||||
}
|
||||
@inject IConfiguration config;
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
}
|
||||
@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">@translator.Translate(userLanguage, "Database Migration")</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Instructions</li>
|
||||
<li class="list-group-item">Use this tool to migrate data between LiteDB and Postgres</li>
|
||||
<li class="list-group-item">Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-3">
|
||||
<div class="d-grid">
|
||||
<input onChange="importPostgresData(this)" type="file" accept=".db" class="d-none" id="inputImport">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="importToPostgres()"><i class="bi bi-upload me-2"></i>@translator.Translate(userLanguage, "Import To Postgres")</button>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="exportFromPostgres()"><i class="bi bi-download me-2"></i>@translator.Translate(userLanguage, "Export From Postgres")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function exportFromPostgres(){
|
||||
sloader.show();
|
||||
$.get('/Migration/Export', function (data) {
|
||||
sloader.hide();
|
||||
if (data.success) {
|
||||
window.location.href = data.message;
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
function importPostgresData(event) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", event.files[0]);
|
||||
sloader.show();
|
||||
$.ajax({
|
||||
url: "/Files/HandleFileUpload",
|
||||
data: formData,
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
success: function (response) {
|
||||
if (response.trim() != '') {
|
||||
$.post('/Migration/Import', { fileName: response }, function (data) {
|
||||
sloader.hide();
|
||||
if (data.success) {
|
||||
successToast(data.message);
|
||||
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
sloader.hide();
|
||||
errorToast("An error has occurred, please check the file size and try again later.");
|
||||
}
|
||||
});
|
||||
}
|
||||
function importToPostgres() {
|
||||
$("#inputImport").click();
|
||||
}
|
||||
</script>
|
||||
@@ -23,11 +23,11 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="collisionRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="collisionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when repaired")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="collisionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when repaired")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="collisionRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) repaired(i.e. Alternator)")" value="@Model.Description">
|
||||
<label for="collisionRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
|
||||
@@ -39,6 +39,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="collisionRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="gasRecordMileage">@($"{translator.Translate(userLanguage,"Odometer Reading")}({distanceUnit})")</label>
|
||||
<input type="number" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when refueled")" value="@(isNew ? "" : Model.GasRecord.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when refueled")" value="@(isNew ? "" : Model.GasRecord.Mileage)">
|
||||
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
|
||||
<input type="text" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<input type="text" inputmode="decimal" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Is Filled To Full")</label>
|
||||
@@ -67,7 +67,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<div class="input-group-text">
|
||||
<select class="form-select form-select-sm" id="gasCostType">
|
||||
<option value="total">@translator.Translate(userLanguage,"Total")</option>
|
||||
@@ -77,7 +77,7 @@
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
}
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag">
|
||||
@@ -86,6 +86,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.GasRecord.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="gasRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="odometerRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
@@ -31,6 +31,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
|
||||
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
|
||||
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
<input type="text" inputmode="decimal" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "PlanRecord")
|
||||
@@ -43,6 +43,14 @@
|
||||
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
||||
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
@if (!isNew)
|
||||
{
|
||||
<label>@($"{translator.Translate(userLanguage, "Date Created")}: {Model.DateCreated}")</label>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<label class="form-check-label" for="reminderMetricOdometer">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="number" id="reminderMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Odometer Reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="reminderMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Odometer Reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="appendMileageToOdometer(500)">+500</button>
|
||||
</div>
|
||||
@@ -72,7 +72,9 @@
|
||||
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
||||
</select>
|
||||
<label for="reminderRecurringMonth">Month</label>
|
||||
<select class="form-select" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
|
||||
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
|
||||
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
|
||||
<!option value="OneYear" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneYear ? "selected" : "")>@translator.Translate(userLanguage, "1 Year")</!option>
|
||||
@@ -102,7 +104,8 @@
|
||||
</div>
|
||||
<script>
|
||||
var customMileageInterval = @Model.CustomMileageInterval;
|
||||
var customMonthInterval = @Model.CustomMonthInterval;
|
||||
function getReminderRecordModelData() {
|
||||
return { id: @Model.Id, mileageInterval: '@Model.ReminderMileageInterval.ToString()'}
|
||||
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
|
||||
}
|
||||
</script>
|
||||
@@ -23,11 +23,11 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="serviceRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="serviceRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when serviced")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="serviceRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when serviced")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="serviceRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) serviced(i.e. Oil Change)")" value="@Model.Description">
|
||||
<label for="serviceRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "ServiceRecord")
|
||||
@@ -39,6 +39,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="serviceRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -31,13 +31,28 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordQuantity">@translator.Translate(userLanguage,"Quantity")</label>
|
||||
<input type="text" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "1" : Model.Quantity)">
|
||||
<input type="text" inputmode="decimal" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "1" : Model.Quantity)">
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
|
||||
</div>
|
||||
</div>
|
||||
<label for="supplyRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="supplyRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -6,13 +6,24 @@
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
}
|
||||
@model List<SupplyRecord>
|
||||
<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">@($"{translator.Translate(userLanguage,"# of Supply Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Supply Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('supply-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
@@ -59,13 +70,13 @@
|
||||
<tbody>
|
||||
@foreach (SupplyRecord supplyRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditSupplyRecordModal(@supplyRecord.Id)">
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditSupplyRecordModal(@supplyRecord.Id)" data-tags='@string.Join(" ", supplyRecord.Tags)'>
|
||||
<td class="col-2 col-xl-1">@supplyRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2">@supplyRecord.PartNumber</td>
|
||||
<td class="col-2">@supplyRecord.PartSupplier</td>
|
||||
<td class="col-2 col-xl-3">@supplyRecord.Description</td>
|
||||
<td class="col-1">@supplyRecord.Quantity</td>
|
||||
<td class="col-1">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-1" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-2 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
}
|
||||
@model List<SupplyRecord>
|
||||
<div class="modal-header">
|
||||
@@ -14,28 +15,36 @@
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
|
||||
<div class="col-12" style="max-height:50vh; overflow-y:auto;" id="supplies-table">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
@translator.Translate(userLanguage,"Supplies are requisitioned immediately after the record is created and cannot be modified. If you have incorrectly entered the amount you needed you will need to correct it in the Supplies tab.")
|
||||
</div>
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('supplies-table', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1"></th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage,"Quantity")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "In Stock")</th>
|
||||
<th scope="col" class="col-5">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (SupplyRecord supplyRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" id="supplyRows">
|
||||
<tr class="d-flex" id="supplyRows" data-tags='@string.Join(" ", supplyRecord.Tags)'>
|
||||
<td class="col-1"><input class="form-check-input" type="checkbox" onchange="toggleQuantityFieldDisabled(this)" value="@supplyRecord.Id"></td>
|
||||
<td class="col-2"><input type="text" disabled onchange="recalculateTotal()" class="form-control"></td>
|
||||
<td class="col-2"><input type="text" inputmode="decimal" disabled onchange="recalculateTotal()" class="form-control"></td>
|
||||
<td class="col-2 supplyquantity">@supplyRecord.Quantity</td>
|
||||
<td class="col-5">@supplyRecord.Description</td>
|
||||
<td class="col-2 text-truncate">@StaticHelper.TruncateStrings(supplyRecord.PartNumber)</td>
|
||||
<td class="col-3 text-truncate">@StaticHelper.TruncateStrings(supplyRecord.Description)</td>
|
||||
<td class="col-2 supplyprice">@((supplyRecord.Quantity > 0 ? supplyRecord.Cost / supplyRecord.Quantity : 0).ToString("F"))</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -120,7 +129,7 @@
|
||||
$("#selectSuppliesButton").attr('disabled', (hasError || totalSum == 0));
|
||||
if (!hasError) {
|
||||
return {
|
||||
totalSum: totalSum,
|
||||
totalSum: globalFloatToString(totalSum),
|
||||
selectedSupplies: selectedSupplies.toArray()
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<label for="taxRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="taxRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of tax paid(i.e. Registration)")" value="@Model.Description">
|
||||
<label for="taxRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
|
||||
<label for="taxRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="taxRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
@@ -33,6 +33,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="taxRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
@@ -42,7 +50,8 @@
|
||||
<label class="form-check-label" for="taxIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
|
||||
</div>
|
||||
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Month")</label>
|
||||
<select class="form-select" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<select class="form-select" onchange="checkCustomMonthIntervalForTax()" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="Other" @(Model.RecurringInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.RecurringInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage,"1 Month")</!option>
|
||||
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage, "3 Months")</!option>
|
||||
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage, "6 Months")</!option>
|
||||
@@ -97,6 +106,7 @@
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var customMonthInterval = @Model.CustomMonthInterval;
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
@@ -105,6 +115,6 @@
|
||||
}
|
||||
}
|
||||
function getTaxRecordModelData() {
|
||||
return { id: @Model.Id}
|
||||
return { id: @Model.Id, monthInterval: decodeHTMLEntities('@Model.RecurringInterval.ToString()') }
|
||||
}
|
||||
</script>
|
||||
@@ -23,11 +23,11 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="upgradeRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="upgradeRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when upgraded/modded")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" inputmode="numeric" id="upgradeRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when upgraded/modded")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="upgradeRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="upgradeRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) upgraded/modded")" value="@Model.Description">
|
||||
<label for="upgradeRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "UpgradeRecord")
|
||||
@@ -39,6 +39,14 @@
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
@foreach (ExtraField field in Model.ExtraFields)
|
||||
{
|
||||
var elementId = Guid.NewGuid();
|
||||
<div class="extra-field">
|
||||
<label for="@elementId">@field.Name</label>
|
||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="upgradeRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="inputYear">@translator.Translate(userLanguage, "Year")</label>
|
||||
<input type="number" id="inputYear" class="form-control" placeholder="@translator.Translate(userLanguage, "Year(must be after 1900)")" value="@(isNew ? "" : Model.Year)">
|
||||
<input type="number" inputmode="numeric" id="inputYear" class="form-control" placeholder="@translator.Translate(userLanguage, "Year(must be after 1900)")" value="@(isNew ? "" : Model.Year)">
|
||||
<label for="inputMake">@translator.Translate(userLanguage, "Make")</label>
|
||||
<input type="text" id="inputMake" class="form-control" placeholder="@translator.Translate(userLanguage, "Make")" value="@Model.Make">
|
||||
<label for="inputModel">@translator.Translate(userLanguage, "Model")</label>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"HideZero": false,
|
||||
"EnableAutoReminderRefresh": false,
|
||||
"EnableAutoOdometerInsert": false,
|
||||
"EnableShopSupplies": false,
|
||||
"UseUKMPG": false,
|
||||
"UseThreeDecimalGasCost": true,
|
||||
"UseMarkDownOnSavedNotes": false,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"UseDarkMode":true,"EnableCsvImports":false,"UseMPG":true,"UseDescending":false,"EnableAuth":false,"UserNameHash":"","UserPasswordHash":""}
|
||||
14
docs/documentation/API.md
Normal file
14
docs/documentation/API.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# API
|
||||
|
||||
LubeLogger provides API endpoints to retrieve and add records, full documentation of these endpoints can be found at `/api`.
|
||||
|
||||
## Authentication
|
||||
If authentication is enabled, it implements Basic Auth based on RFC2617, which stipulates that the "token" is passed in as a Base64-encoded string comprising of a username and password separated by a colon(":"). Because of this, neither the username nor password can contain a colon(":") character.
|
||||
|
||||
### Testing
|
||||
You can utilize any REST API testing tool to test your use-case.
|
||||
|
||||
## Example Use Cases
|
||||
- Send Email Reminders, see [[Reminders|Records/Reminders#reminder-emails]]
|
||||
- Insert Odometer Records, see [[Odometer|Records/Odometer#api-integration]]
|
||||
- Create DB Backups
|
||||
45
docs/documentation/Authentication.md
Normal file
45
docs/documentation/Authentication.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Authentication
|
||||
|
||||
LubeLogger does not require authentication by default; however, it is highly recommend that you set up authentication if your LubeLogger instance is accessible vvia the Internet or if you wish to invite other users to your instance.
|
||||
|
||||
## Enabling Authentication
|
||||
To enable authentication, all you have to do is navigate to the "Settings" tab and check "Enable Authentication".
|
||||
|
||||
A dialog will then prompt you to enter a username and password. These are the credentials for the Root/Super User.
|
||||
|
||||
Once you have entered the credentials, click the "Setup" button and you will be redirected to a login screen, enter the credentials of the Root/Super User here to login.
|
||||
|
||||
## Creating / Inviting New Users
|
||||
LubeLogger relies on an invitation-only model for creating/registering new users. It is highly recommended that LubeLogger is configured with SMTP in order to make the user registration process as smooth as possible, see [[Getting Started]] for more information regarding SMTP configuration.
|
||||
|
||||
To Create/Invite New Users, you first need to enable authentication and set up the Root User credentials. Once that is done, upon login, you will see that there is now a dropdown to the right of the "Settings" tab that has your root username on it. Click on that dropdown and select "Admin Panel"
|
||||

|
||||
|
||||
You will now be taken to a new page. There are two sections in this page, Tokens and Users.
|
||||

|
||||
|
||||
Tokens are used for invitees to register their user account or existing users to reset their password. These tokens are single use and are validated against the email address they are issued for.
|
||||
|
||||
Users, as the name suggests, is a list of users in the system. You can also mark or unmark existing users as Admins here, see below to understand what permissions Admin users have.
|
||||
|
||||
To invite a user, simply click on the "Generate User Token" button and type in their email address, note that this is case-sensitive.
|
||||

|
||||
|
||||
If SMTP is configured and the Auto Notify(via Email) switch is checked, the user will receive an email that looks like this:
|
||||

|
||||
|
||||
## Root/Super User
|
||||
You might be tempted to use your root credentials as your main credentials, and there is nothing wrong that, but you should know that there are a few caveats associated with the root user.
|
||||
1. Root/Super Users can view and edit all vehicles, this might seem like a great advantage initially, but it can be problematic if there are sufficient users and you have to sift through dozens of vehicles to get to yours.
|
||||
2. Any setting you enable/disable will become the default setting inherited by new users.
|
||||
|
||||
For the reasons above, it is highly recommended that you create a second user for personal use and mark it as an Admin. See below for a breakdown of permissions across user tiers.
|
||||
|
||||
| Permissions | User | Admin | Root/Super User |
|
||||
| ---------------------- | ----------------------------------------- | ----------------------------------------- | ---------------------- |
|
||||
| View/Edit Vehicles | Only vehicles which they are collaborator | Only vehicles which they are collaborator | View/Edit All Vehicles |
|
||||
| Access API | Yes | Yes | Yes |
|
||||
| Personalized Settings | Yes | Yes | No, settings will be set as server default |
|
||||
| Add/Remove Users | No | Yes | Yes |
|
||||
| Make/Restore Backups | No | No | Yes |
|
||||
| Disable Authentication | No | No | Yes |
|
||||
@@ -1,21 +0,0 @@
|
||||
# Sharing your Vehicles and Collaborating with Other Users
|
||||
|
||||
LubeLogger allows you to collaborate on vehicles so that more than one user can add or edit records on a vehicle.
|
||||
|
||||
To share a vehicle, simply navigate into the Vehicle's Dashboard, and look to the bottom left
|
||||
|
||||

|
||||
|
||||
Click on the little blue button with a User Add icon and you will be prompted to enter the user's username
|
||||
|
||||

|
||||
|
||||
**Note:** The username is case sensitive and the user must exist in the system.
|
||||
|
||||
Once you have added the user, their username will then show up in the list of Collaborators
|
||||
|
||||

|
||||
|
||||
Now the user can view and edit the vehicle as well:
|
||||
|
||||

|
||||
@@ -1,51 +0,0 @@
|
||||
# Configuring LubeLogger
|
||||
In order to provide the best possible user experience, we have provided ample amount of flexibility when it comes to user settings.
|
||||
Upon initial launch, you are using the Root User by default without any authentication, so you will have access to all of the settings.
|
||||
|
||||

|
||||
|
||||
Most of the settings are relatively straightforward and self-explanatory.
|
||||
|
||||
**Note:** If you are a user in the UK and you wish be able to input Fuel Purchases in Liters but display Fuel Mileage as Miles Per UK Gallons, you will need to enable "Use Imperial Calculation" and "Use UK MPG Calculation"
|
||||
|
||||
**Note:** When making changes to Settings as a root user, your settings will be saved and served up as the default server settings for any new users that sign up.
|
||||
|
||||
## Enable Authentication
|
||||
It is highly recommended that you secure your LubeLogger instance by enabling authentication.
|
||||
To do so, simply check "Enable Authentication" and you will be prompted to enter a Username and Password
|
||||
|
||||

|
||||
|
||||
The credentials that you set up here are the credentials for the Root User, aka the Super Admin, and shouldn't be shared with anyone else.
|
||||
|
||||
Once you have entered the credentials, you will then be redirected to a Login page
|
||||
|
||||

|
||||
|
||||
Simply enter the credentials you have just set up and you will be logged right in
|
||||
|
||||

|
||||
|
||||
## Setting Up Multiple Users
|
||||
To set up multiple users, all you have to do is click on the dropdown that has your username on it and select "Admin Panel"
|
||||
|
||||

|
||||
|
||||
If you have SMTP configured correctly, the "Auto Notify(via Email) switch will be enabled and checked, otherwise it will be disabled/grayed out.
|
||||
Without SMTP Configured:
|
||||
|
||||

|
||||
|
||||
With SMTP Configured:
|
||||
|
||||

|
||||
|
||||
To generate a new user token, simply click on the "Generate User Token" button and you will be prompted with the user's email address
|
||||
|
||||

|
||||
|
||||
If you have SMTP Configured, the user will then receive an Email that looks similar to this:
|
||||

|
||||
|
||||
The user can then proceed to Register for an account at your instance of LubeLogger.
|
||||
|
||||
32
docs/documentation/Dashboard.md
Normal file
32
docs/documentation/Dashboard.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Dashboard
|
||||
|
||||
The Dashboard is where you get an overview of your vehicle. It is the default tab that the user will see when they click into a vehicle and it is also the only tab that cannot be hidden.
|
||||
|
||||

|
||||
|
||||
The Dashboard includes the following data:
|
||||
- Expenses By Type By Year/All Time(Top-Left)
|
||||
- Total Expenses By Month By Year/All Time(Top-Center)
|
||||
- Reminders by current date or future date(Top-Right)
|
||||
- Collaborators(Bottom-Left), see [[Adding Collaborators|Vehicle Management#adding-a-collaborator]]
|
||||
- Fuel Mileage By Month By Year/All Time(Bottom-Center)
|
||||
- Vehicle Maintenance Report Generator(Bottom-Right)
|
||||
|
||||
## Filtering Data by Year
|
||||
The year dropdown above the pie chart(top-left) allows the user to filter the aggregated data by year. The year selections are populated by retrieving all years between the year of the oldest record and the current year. If the oldest record is less than 5 years old, the selections will still be populated by the last 5 years.
|
||||
|
||||
Note: Changing the selected year will automatically refresh the Expenses by Type Pie Chart, the Total Expenses by Month and the Fuel Mileage By Month Bar Charts.
|
||||
|
||||

|
||||
|
||||
## Vehicle Maintenance Report
|
||||
The Vehicle Maintainence Report Generator is a button that will generate a consolidated report of all work performed on the vehicle. For performance reasons, this report is designed to be printed as soon as it is generated. You can either choose to print it to paper or PDF.
|
||||
|
||||

|
||||
|
||||
## Export Attachments
|
||||
The Export Attachments button provies a convenient feature to export all attachments into a chronologically-ordered zip file. Upon clicking the button, you will be prompted to select which tabs you want attachments to be exported from.
|
||||
|
||||

|
||||
|
||||
Once you have made your selection, a zip file will be created and downloaded onto your computer. This zip file contains all of the attachments and are named in chronological order(i.e.: the oldest attachment will be named 0 and the second oldest attachment 1, 2...)
|
||||
62
docs/documentation/FuelRecords.md
Normal file
62
docs/documentation/FuelRecords.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Fuel Records
|
||||
|
||||
The Fuel tab keeps track of the fuel mileage for your vehicle.
|
||||
|
||||

|
||||
|
||||
LubeLogger supports fuel mileage calculation in the following formats:
|
||||
- American Imperial (MPG)
|
||||
- European/Asian Metric (L/100Km)
|
||||
- British(Purchase Gas in Liters and calculate fuel mileage as Miles per Imperial UK Gallons)
|
||||
- Electric Vehicles (mi./kWh or kWh/100Km)
|
||||
|
||||
## Initial Fuel Up
|
||||
In order to calculate fuel mileage, you must first have an initial fuel entry with the current odometer reading. An odometer reading is needed so that the app can calculate the distance traveled between fuel ups. It is recommended that you fill it up to full for the first entry.
|
||||
|
||||
## Imperfect Fuel Ups
|
||||
For the most accurate results it is recommended that you always fill your vehicle up to full and not miss any fuel ups, but sometimes things happen, which is why we provided the following:
|
||||
|
||||
### Partial Fuel Ups
|
||||
On the occassions that you cannot fill your vehicle up to full, you can defer the fuel mileage calculation by unchecking the "Is Filled To Full" switch. Doing this tells the app to defer fuel mileage calculation until the next Full Fill Up.
|
||||
|
||||

|
||||
|
||||
### Missed Fuel Ups
|
||||
Check this if you have missed a fuel up record prior to adding this fuel record. This effectively resets the fuel mileage calculation and will show up as $0 or "---" in the fuel records. Checking this ensures that the average fuel mileage calculation isn't skewed due to missed fuel ups.
|
||||
|
||||
## Calculation of Average Fuel Mileage
|
||||
Average MPG is calculated by excluding the initial and missed fuel ups, then taking the difference between the min and max odometer reading and dividing it by total amount of gas consumed(if Metric then it is further divided by 1/100). This method will include all of the gas consumed by full and partial fuel ups.
|
||||
|
||||
## Fuel Units
|
||||
The consumption, fuel mileage, and odometer units are determined by two settings: "Use Imperial Calculation" and "Use UK MPG Calculation"
|
||||
|
||||
| Setting | Use UK MPG Calculation Checked | Use UK MPG Calculation Unchecked |
|
||||
| -------- | -------- | -------- |
|
||||
| Use Imperial Calculation Checked | Distance: Miles, Consumption: Liters, Fuel Mileage: Miles per UK Gallons | Distance: Miles, Consumption: Gallons, Fuel Mileage: MPG |
|
||||
| Use Imperial Calculation Unchecked | Distance: Miles, Consumption: Liters, Fuel Mileage: l/100mi. | Distance: Km, Consumption: Liters, Fuel Mileage: l/100km |
|
||||
|
||||
### Alternate Fuel Units
|
||||
If you wish to see alternate units which converts the calculated units within the Gas Tab, you can right click on the table headers for Consumption and Fuel Economy. These settings persists for the user, so the next time you login to LubeLogger it will automatically perform the conversion for you.
|
||||
|
||||

|
||||
|
||||
#### Consumption
|
||||
For consumption, the units are cycled between US gal, Liters, and Imp Gal(UK). Changing the consumption unit will also change the unit cost.
|
||||
|
||||
#### Fuel Economy
|
||||
The units can only toggle between l/100km and km/l, which means that this unit cannot be converted if your fuel economy unit is not l/100km. Changing the fuel economy unit will also update the values in the Average, Min, and Max Fuel Economy labels as well as the Fuel Economy Unit in the Consolidated Report.
|
||||
|
||||
## Importing from CSV/Fuelly/SpiritMonitor.de
|
||||
LubeLogger supports importing CSV exports from other apps, below lists the column names that are acceptable/mapped to our data points:
|
||||
|
||||
| LubeLogger Data Field | Imported CSV |
|
||||
| -------------------------------------- | -------------------------------------------------------------- |
|
||||
| date | date, fuelup_date |
|
||||
| odometer | odometer |
|
||||
| fuelconsumed | gallons, liters, litres, consumption, quantity, fuelconsumed |
|
||||
| cost | cost, total cost, totalcost, total price |
|
||||
| notes | notes, note |
|
||||
| partialfuelup(inverse of isfilltofull) | partial_fuelup |
|
||||
| isfilltofull | isfilltofull, filled up |
|
||||
| missedfuelup | missedfuelup, missed_fuelup |
|
||||
| tags | tags |
|
||||
@@ -27,7 +27,7 @@ By default the app will start listening at localhost:8080, this port can be conf
|
||||
## Windows Standalone Executable
|
||||
Windows Standalone executables are provided on a request basis, and will usually be included with every other release.
|
||||
|
||||
To run the server, you just have to double click on CarCareTracker.exe
|
||||
To run the server, you just have to download the zip archive attached to the release, usually named LubeLogger_vNNN_win_x64.zip, extract the archive and double click on CarCareTracker.exe
|
||||
|
||||
Occassionally you might run into an issue regarding a missing folder, to fix that, just create a "config" folder where CarCareTracker.exe is located.
|
||||
|
||||
@@ -71,5 +71,3 @@ When using this approach, the default port the app will be listening on is 5000,
|
||||
|
||||
## Test that It Works
|
||||
Whichever path you choose, once you get the app up and running, just navigate to the IP address and port the server is listening to and you should be able to see the app
|
||||

|
||||

|
||||
|
||||
46
docs/documentation/HTTPS.md
Normal file
46
docs/documentation/HTTPS.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Set Up HTTPS
|
||||
|
||||
LubeLogger runs on Kestrel, which is a cross-platform standalone web server provided by .NET
|
||||
|
||||
If you're running LubeLogger behind a reverse proxy(i.e. NGINX), then this walkthrough does not apply to you since the SSL certs will be served up by NGINX instead of Kestrel.
|
||||
|
||||
This article covers the step-by-step process to set up HTTPS for a LubeLogger instance.
|
||||
|
||||
## Docker
|
||||
If you're running LubeLogger on a Docker instance, first read [this article by Microsoft](https://learn.microsoft.com/en-us/aspnet/core/security/docker-compose-https?view=aspnetcore-8.0)
|
||||
|
||||
1. Convert the .PEM / .CRT files into .PFX, read [this StackOverflow post](https://stackoverflow.com/questions/808669/convert-a-cert-pem-certificate-to-a-pfx-certificate)
|
||||
2. Open and modify the .env file and add the following lines(note that in this example I used bob as the password for the cert)
|
||||
```
|
||||
ASPNETCORE_Kestrel__Certificates__Default__Password=bob
|
||||
ASPNETCORE_Kestrel__Certificates__Default__Path=/https/<yourPFXCertificateName>.pfx
|
||||
ASPNETCORE_URLS=https://+:443;http://+:80
|
||||
```
|
||||
3. Open and modify docker-compose.yml. You will need to bind a new volume to the Docker container so that Kestrel can access the certificate file.
|
||||
```
|
||||
volumes:
|
||||
- ~/https/:/https:ro
|
||||
```
|
||||
4. Run `docker-compose up -d` to start up the container and `https://localhost` will now have a valid cert.
|
||||
|
||||
## Windows
|
||||
If you're running LubeLogger as the standalone Windows executable, first read [this article by Microsoft](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-8.0#configure-https-in-appsettingsjson)
|
||||
|
||||
1. Convert the .PEM / .CRT files into .PFX, read [this StackOverflow post](https://stackoverflow.com/questions/808669/convert-a-cert-pem-certificate-to-a-pfx-certificate)
|
||||
2. Open and modify appsettings.json located in the same directory as the CarCareTracker executable and add the following lines(note that in this example I used bob as the password for the cert)
|
||||
```
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://localhost:80"
|
||||
},
|
||||
"HttpsInlineCertFile": {
|
||||
"Url": "https://localhost:443",
|
||||
"Certificate": {
|
||||
"Path": "<path to .pfx file>",
|
||||
"Password": "bob"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Restart the app and `https://localhost` will now have a valid cert.
|
||||
25
docs/documentation/Notes.md
Normal file
25
docs/documentation/Notes.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Notes
|
||||
|
||||
The Notes tab contains important notes about your vehicle.
|
||||
|
||||
## Markdown Parsing
|
||||
Markdown formatting is supported across all Notes fields in the app. The underlying markdown parser is [Drawdown](https://github.com/adamvleggett/drawdown). To toggle between edit and preview mode, simply click on the markdown icon next to the "Notes" label.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
There is also a setting, that when enabled, will automatically load all notes in Markdown form when viewing existing records. The only exception is when an existing record has an empty notes field.
|
||||
|
||||
## Pinned Notes
|
||||
Notes can be pinned by checking the "Pinned" switch when creating or editing a note.
|
||||
|
||||

|
||||
|
||||
Pinned Notes will always show up at the very top of the list.
|
||||
|
||||

|
||||
|
||||
On non-touchscreen devices, pinned notes can be viewed when the user hovers over the vehicle tile in the garage. On mobile, the user have to hold down on the garage tile in order for the pinned notes to show up.
|
||||
|
||||

|
||||
14
docs/documentation/Odometer.md
Normal file
14
docs/documentation/Odometer.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Odometer
|
||||
|
||||
The Odometer tab is where you can log your current odometer reading without having to insert any Service/Repair/Upgrade/Fuel records. This odometer readings entered in this tab allows Reminder urgencies to be calculated as accurately as possible since it uses the maximum mileage reported in each of the tabs to determine the last reported mileage.
|
||||
|
||||
The Odometer tab is hidden by default and must be enabled by checking the "Odometer" switch under "Visible Tabs" in the Settings tab.
|
||||
|
||||
## API Integration
|
||||
As with the other tabs, odometer readings can be retrieved via a GET endpoint and inserted via a POST API endpoint.
|
||||
|
||||
Example use cases:
|
||||
- An app to integrate with OBDII and insert odometer reading from the vehicle's computer onto LubeLogger.
|
||||
- An app to keep track of distance traveled via GPS and incrementing the last reported odometer reading.
|
||||
|
||||
These are not functionalities provided out of the box by LubeLogger, and are just examples of the possibilities achievable via the API endpoints.
|
||||
62
docs/documentation/Planner.md
Normal file
62
docs/documentation/Planner.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Planner
|
||||
|
||||
The Planner tab is where you can plan and track progress of future or current plans for your vehicle. The planner tab consists of 4 swimlanes(vertical panels) where plans can be moved to different stages via drag and drop.
|
||||
|
||||

|
||||
|
||||
The records stored in Service/Repair/Upgrade tabs tend to be inserted retroactively(after the work is done), hence the Planner tab was developed to keep track of To-Do's.
|
||||
|
||||
## Adding a new Plan
|
||||
The Planner tab is hidden by default, you must enable it by checking the "Planner" switch under "Visible Tabs" within the Settings tab.
|
||||
|
||||
To add a new plan, simply click on the "Add Plan Record" button and fill in the details of the plan.
|
||||
|
||||

|
||||
|
||||
## Plan Type
|
||||
You are required to select at least one Plan Type - Service, Repair, or Upgrade. When the Plan Record is moved to Done, the Plan Record will be automatically converted into the selected type and it will then show up in its respective tab. The type is indicated by an icon corresponding to their respective tabs.
|
||||
|
||||
## Plan Priority
|
||||
Within the swimlanes, plans are sorted from Critical priority to Low priority. They are also indicated by an icon - Fire for Critical, Waves for Normal, and Snowflake for Low.
|
||||
|
||||

|
||||
|
||||
## Plan Stages
|
||||
**Planned** - This is considered the backlog, where no work has been performed yet. i.e.: Shopping for a lift kit.
|
||||
|
||||
**Doing** - This is when work has been started on the task. i.e.: Installing the lift kit.
|
||||
|
||||
**Testing** - This is where the work is being reviewed / tested. i.e.: Going for a test drive with the lift kit.
|
||||
|
||||
**Done** - This is where the work is marked as completed.
|
||||
|
||||
### Updating Plan Stages
|
||||
There are two ways you can update the stage a plan is currently in: drag and drop or updating it via the dropdown when editing a plan. You can backtrack a plan's stages within Planned, Doing, and Testing. i.e.: You can move the plan back from Testing to Doing if you find out there is more work to be done.
|
||||
|
||||
### Moving to Done
|
||||
when a Plan is being moved to Done, it will prompt you to enter the current odometer reading. This is to ensure that the Service/Repair/Upgrade record that will be created from it contains the most up-to-date odometer reading.
|
||||
|
||||
Once a Plan has been marked as Done, it can never be taken out of it. The only action that can be performed on it is Delete. You can delete Plans in the Done stage by clicking on them.
|
||||
|
||||
## Plan Templates
|
||||
For records that have a fixed interval(i.e. oil changes), it is recommended that you create a Plan Template instead.
|
||||
|
||||
### Creating a Template
|
||||
To create a template, simply fill in the details / select the supplies to requisition for the plan, but instead of clicking the "Add New Plan Record" button, you will click on the dropdown next to it and select "Save As Template"
|
||||
|
||||

|
||||
|
||||
The template will now be created and named after the description of the plan record.
|
||||
|
||||
### Using Templates
|
||||
To create a plan record from a template, click on the "View Templates button" and a dialog will show up with the saved templates for the vehicle
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The shop icon indicates if the template has supplies requisition and the paper clip icon(not shown) indicates if the template has file attachments.
|
||||
|
||||
To use the template, simply click on the "+" button. The app will then perform a check to ensure that there are sufficient quantities of the supplies used by the template. If there are insufficient quantities, an error message will pop up telling you which supply is short.
|
||||
|
||||

|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user