From 32e58e62809ea6e70ded9c7d3435e33ad326b3c8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Fri, 9 Feb 2024 13:34:10 -0700 Subject: [PATCH] added import function for migration tool. --- Controllers/MigrationController.cs | 374 ++++++++++++++++++ .../Postgres/CollisionRecordDataAccess.cs | 2 +- .../Postgres/GasRecordDataAccess.cs | 2 +- .../Postgres/NoteDataAccess.cs | 2 +- .../Postgres/OdometerRecordDataAccess.cs | 2 +- .../Postgres/PlanRecordDataAccess.cs | 2 +- .../Postgres/PlanRecordTemplateDataAccess.cs | 2 +- .../Postgres/ReminderRecordDataAccess.cs | 2 +- .../Postgres/ServiceRecordDataAccess.cs | 2 +- .../Postgres/SupplyRecordDataAccess.cs | 2 +- .../Postgres/TaxRecordDataAccess.cs | 2 +- .../Postgres/TokenRecordDataAccess.cs | 2 +- .../Postgres/UpgradeRecordDataAccess.cs | 2 +- .../Postgres/UserConfigDataAccess.cs | 5 +- .../Postgres/UserRecordDataAccess.cs | 2 +- .../Postgres/VehicleDataAccess.cs | 2 +- Helper/ConfigHelper.cs | 5 + Views/Migration/Index.cshtml | 77 ++++ wwwroot/defaults/en_US.json | 2 +- 19 files changed, 473 insertions(+), 18 deletions(-) create mode 100644 Controllers/MigrationController.cs create mode 100644 Views/Migration/Index.cshtml diff --git a/Controllers/MigrationController.cs b/Controllers/MigrationController.cs new file mode 100644 index 0000000..f59ddb8 --- /dev/null +++ b/Controllers/MigrationController.cs @@ -0,0 +1,374 @@ +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.Xml.Linq; + +namespace CarCareTracker.Controllers +{ + [Authorize(Roles = nameof(UserData.IsRootUser))] + public class MigrationController : Controller + { + private IConfigHelper _configHelper; + private IConfiguration _serverConfig; + private IFileHelper _fileHelper; + private readonly ILogger _logger; + public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, IConfiguration serverConfig, ILogger logger) + { + _configHelper = configHelper; + _fileHelper = fileHelper; + _serverConfig = serverConfig; + _logger = logger; + } + public IActionResult Index() + { + if (_configHelper.GetServerHasPostgresConnection()) + { + return View(); + } else + { + return new RedirectResult("/Error/Unauthorized"); + } + } + private void InitializeTables(NpgsqlConnection conn) + { + var cmds = new List + { + "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))" + }; + foreach(string cmd in cmds) + { + using (var ctext = new NpgsqlCommand(cmd, conn)) + { + ctext.ExecuteNonQuery(); + } + } + } + public IActionResult Import(string fileName) + { + if (!_configHelper.GetServerHasPostgresConnection()) + { + return new RedirectResult("/Error/Unauthorized"); + } + var fullFileName = _fileHelper.GetFullFilePath(fileName); + if (string.IsNullOrWhiteSpace(fullFileName)) + { + return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }); + } + try + { + var pgDataSource = new NpgsqlConnection(_serverConfig["POSTGRES_CONNECTION"]); + pgDataSource.Open(); + InitializeTables(pgDataSource); + //pull records + var vehicles = new List(); + var repairrecords = new List(); + var upgraderecords = new List(); + var servicerecords = new List(); + + var gasrecords = new List(); + var noterecords = new List(); + var odometerrecords = new List(); + var reminderrecords = new List(); + + var planrecords = new List(); + var planrecordtemplates = new List(); + var supplyrecords = new List(); + var taxrecords = new List(); + + var userrecords = new List(); + var tokenrecords = new List(); + var userconfigrecords = new List(); + var useraccessrecords = new List(); + #region "Part1" + using (var db = new LiteDatabase(fullFileName)) + { + var table = db.GetCollection("vehicles"); + vehicles = table.FindAll().ToList(); + }; + foreach(var vehicle in vehicles) + { + string cmd = $"INSERT INTO app.vehicles (id, data) VALUES(@id, CAST(@data AS jsonb))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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))"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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)"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("tokenrecords"); + tokenrecords = table.FindAll().ToList(); + }; + foreach (var record in tokenrecords) + { + string cmd = $"INSERT INTO app.tokenrecords (id, emailaddress, body) VALUES(@id, @emailaddress, @body)"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("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 = new NpgsqlCommand(cmd, pgDataSource)) + { + 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("useraccessrecords"); + useraccessrecords = table.FindAll().ToList(); + }; + foreach (var record in useraccessrecords) + { + string cmd = $"INSERT INTO app.useraccessrecords (userId, vehicleId) VALUES(@userId, @vehicleId)"; + using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) + { + ctext.Parameters.AddWithValue("userId", record.Id.UserId); + ctext.Parameters.AddWithValue("vehicleId", record.Id.VehicleId); + 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 }); + } + } + } +} diff --git a/External/Implementations/Postgres/CollisionRecordDataAccess.cs b/External/Implementations/Postgres/CollisionRecordDataAccess.cs index 9fa7e32..ee6e8d8 100644 --- a/External/Implementations/Postgres/CollisionRecordDataAccess.cs +++ b/External/Implementations/Postgres/CollisionRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/GasRecordDataAccess.cs b/External/Implementations/Postgres/GasRecordDataAccess.cs index 1301dab..9f3ce34 100644 --- a/External/Implementations/Postgres/GasRecordDataAccess.cs +++ b/External/Implementations/Postgres/GasRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/NoteDataAccess.cs b/External/Implementations/Postgres/NoteDataAccess.cs index 6540a34..88ea7a9 100644 --- a/External/Implementations/Postgres/NoteDataAccess.cs +++ b/External/Implementations/Postgres/NoteDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/OdometerRecordDataAccess.cs b/External/Implementations/Postgres/OdometerRecordDataAccess.cs index feab197..b4527a0 100644 --- a/External/Implementations/Postgres/OdometerRecordDataAccess.cs +++ b/External/Implementations/Postgres/OdometerRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/PlanRecordDataAccess.cs b/External/Implementations/Postgres/PlanRecordDataAccess.cs index 10cb76b..ff34a48 100644 --- a/External/Implementations/Postgres/PlanRecordDataAccess.cs +++ b/External/Implementations/Postgres/PlanRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs b/External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs index c40e916..7c850f2 100644 --- a/External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs +++ b/External/Implementations/Postgres/PlanRecordTemplateDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/ReminderRecordDataAccess.cs b/External/Implementations/Postgres/ReminderRecordDataAccess.cs index b006706..30eefdf 100644 --- a/External/Implementations/Postgres/ReminderRecordDataAccess.cs +++ b/External/Implementations/Postgres/ReminderRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/ServiceRecordDataAccess.cs b/External/Implementations/Postgres/ServiceRecordDataAccess.cs index 4449a44..2405d08 100644 --- a/External/Implementations/Postgres/ServiceRecordDataAccess.cs +++ b/External/Implementations/Postgres/ServiceRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/SupplyRecordDataAccess.cs b/External/Implementations/Postgres/SupplyRecordDataAccess.cs index b299644..618acbf 100644 --- a/External/Implementations/Postgres/SupplyRecordDataAccess.cs +++ b/External/Implementations/Postgres/SupplyRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/TaxRecordDataAccess.cs b/External/Implementations/Postgres/TaxRecordDataAccess.cs index a895336..30158d3 100644 --- a/External/Implementations/Postgres/TaxRecordDataAccess.cs +++ b/External/Implementations/Postgres/TaxRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/TokenRecordDataAccess.cs b/External/Implementations/Postgres/TokenRecordDataAccess.cs index c785525..091b53e 100644 --- a/External/Implementations/Postgres/TokenRecordDataAccess.cs +++ b/External/Implementations/Postgres/TokenRecordDataAccess.cs @@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/UpgradeRecordDataAccess.cs b/External/Implementations/Postgres/UpgradeRecordDataAccess.cs index c1e00bc..ff336db 100644 --- a/External/Implementations/Postgres/UpgradeRecordDataAccess.cs +++ b/External/Implementations/Postgres/UpgradeRecordDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/UserConfigDataAccess.cs b/External/Implementations/Postgres/UserConfigDataAccess.cs index e4e5f68..cc33cc7 100644 --- a/External/Implementations/Postgres/UserConfigDataAccess.cs +++ b/External/Implementations/Postgres/UserConfigDataAccess.cs @@ -34,7 +34,7 @@ namespace CarCareTracker.External.Implementations try { string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id"; - var result = new UserConfigData(); + UserConfigData result = null; using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) { ctext.Parameters.AddWithValue("id", userId); @@ -45,7 +45,6 @@ namespace CarCareTracker.External.Implementations result = userConfig; } } - if (result.UserConfig == null) { return null; } return result; } catch (Exception ex) @@ -59,7 +58,7 @@ namespace CarCareTracker.External.Implementations var existingRecord = GetUserConfig(userConfigData.Id); try { - if (existingRecord == null || existingRecord.Id == default) + if (existingRecord == null) { string cmd = $"INSERT INTO app.{tableName} (id, data) VALUES(@id, CAST(@data AS jsonb))"; using (var ctext = new NpgsqlCommand(cmd, pgDataSource)) diff --git a/External/Implementations/Postgres/UserRecordDataAccess.cs b/External/Implementations/Postgres/UserRecordDataAccess.cs index 13c5de7..0bb63b3 100644 --- a/External/Implementations/Postgres/UserRecordDataAccess.cs +++ b/External/Implementations/Postgres/UserRecordDataAccess.cs @@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/External/Implementations/Postgres/VehicleDataAccess.cs b/External/Implementations/Postgres/VehicleDataAccess.cs index 0165c35..99f4ab9 100644 --- a/External/Implementations/Postgres/VehicleDataAccess.cs +++ b/External/Implementations/Postgres/VehicleDataAccess.cs @@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations { pgDataSource.Open(); //create table if not exist. - string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED ALWAYS AS IDENTITY primary key, data jsonb not null)"; + 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 = new NpgsqlCommand(initCMD, pgDataSource)) { ctext.ExecuteNonQuery(); diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs index e00345f..cee2a35 100644 --- a/Helper/ConfigHelper.cs +++ b/Helper/ConfigHelper.cs @@ -12,6 +12,7 @@ namespace CarCareTracker.Helper string GetLogoUrl(); string GetServerLanguage(); bool GetServerEnableShopSupplies(); + bool GetServerHasPostgresConnection(); public bool DeleteUserConfig(int userId); } public class ConfigHelper : IConfigHelper @@ -41,6 +42,10 @@ namespace CarCareTracker.Helper var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US"; return serverLanguage; } + public bool GetServerHasPostgresConnection() + { + return !string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]); + } public bool GetServerEnableShopSupplies() { return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false"); diff --git a/Views/Migration/Index.cshtml b/Views/Migration/Index.cshtml new file mode 100644 index 0000000..23ede02 --- /dev/null +++ b/Views/Migration/Index.cshtml @@ -0,0 +1,77 @@ +@using CarCareTracker.Helper +@{ + ViewData["Title"] = "Admin"; +} +@inject IConfiguration config; +@inject ITranslationHelper translator +@{ + var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US"; +} +@model AdminViewModel +
+
+
+ +
+
+ @translator.Translate(userLanguage, "Database Migration") +
+
+
+
+
+
    +
  • Instructions
  • +
  • Use this tool to migrate data between LiteDB and Postgres
  • +
  • Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.
  • +
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ \ No newline at end of file diff --git a/wwwroot/defaults/en_US.json b/wwwroot/defaults/en_US.json index f887181..8a0c20f 100644 --- a/wwwroot/defaults/en_US.json +++ b/wwwroot/defaults/en_US.json @@ -1 +1 @@ -{"Garage":"Garage","Settings":"Settings","Admin_Panel":"Admin Panel","Logout":"Logout","Dark_Mode":"Dark Mode","Enable_CSV_Imports":"Enable CSV Imports","Use_Imperial_Calculation_for_Fuel_Economy_Calculations(MPG)":"Use Imperial Calculation for Fuel Economy Calculations(MPG)","This_Will_Also_Change_Units_to_Miles_and_Gallons":"This Will Also Change Units to Miles and Gallons","Use_UK_MPG_Calculation":"Use UK MPG Calculation","Input_Gas_Consumption_in_Liters,_it_will_be_converted_to_UK_Gals_for_MPG_Calculation":"Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation","Sort_lists_in_Descending_Order(Newest_to_Oldest)":"Sort lists in Descending Order(Newest to Oldest)","Replace_$0.00_Costs_with_---":"Replace $0.00 Costs with ---","Use_Three_Decimals_For_Fuel_Cost":"Use Three Decimals For Fuel Cost","Display_Saved_Notes_in_Markdown":"Display Saved Notes in Markdown","Auto_Refresh_Lapsed_Recurring_Reminders":"Auto Refresh Lapsed Recurring Reminders","Auto_Insert_Odometer_Records":"Auto Insert Odometer Records","Only_when_Adding_Service/Repair/Upgrade/Fuel_Record_or_Completing_a_Plan":"Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan","Enable_Authentication":"Enable Authentication","Visible_Tabs":"Visible Tabs","Service_Records":"Service Records","Dashboard":"Dashboard","Repairs":"Repairs","Upgrades":"Upgrades","Fuel":"Fuel","Odometer":"Odometer","Taxes":"Taxes","Notes":"Notes","Reminder":"Reminder","Supplies":"Supplies","Planner":"Planner","Default_Tab":"Default Tab","Service_Record":"Service Record","Tax":"Tax","Reminders":"Reminders","Backups":"Backups","Make":"Make","Restore":"Restore","About":"About","Add_New_Vehicle":"Add New Vehicle","Year":"Year","Year(must_be_after_1900)":"Year(must be after 1900)","Model":"Model","License_Plate":"License Plate","Electric_Vehicle":"Electric Vehicle","Use_Engine_Hours":"Use Engine Hours","Tags(optional)":"Tags(optional)","Upload_a_picture(optional)":"Upload a picture(optional)","Cancel":"Cancel","Edit_Vehicle":"Edit Vehicle","Delete_Vehicle":"Delete Vehicle","Manage_Vehicle":"Manage Vehicle","Expenses_by_Type":"Expenses by Type","Service":"Service","Expenses_by_Month":"Expenses by Month","As_of_Today":"As of Today","\u002B30_Days":"\u002B30 Days","\u002B60_Days":"\u002B60 Days","\u002B90_Days":"\u002B90 Days","Not_Urgent":"Not Urgent","Urgent":"Urgent","Very_Urgent":"Very Urgent","Past_Due":"Past Due","Reminders_by_Category":"Reminders by Category","Reminders_by_Urgency":"Reminders by Urgency","Collaborators":"Collaborators","Username":"Username","Delete":"Delete","Fuel_Mileage_by_Month":"Fuel Mileage by Month","Vehicle_Maintenance_Report":"Vehicle Maintenance Report","Export_Attachments":"Export Attachments","Gasoline":"Gasoline","Last_Reported_Odometer_Reading":"Last Reported Odometer Reading","Average_Fuel_Economy":"Average Fuel Economy","Total_Spent(excl._fuel)":"Total Spent(excl. fuel)","Total_Spent_on_Fuel":"Total Spent on Fuel","Type":"Type","Date":"Date","Description":"Description","Cost":"Cost","Repair":"Repair","Upgrade":"Upgrade","#_of_Odometer_Records":"# of Odometer Records","Add_Odometer_Record":"Add Odometer Record","Import_via_CSV":"Import via CSV","Export_to_CSV":"Export to CSV","Print":"Print","Add_New_Odometer_Record":"Add New Odometer Record","Date_recorded":"Date recorded","Odometer_reading":"Odometer reading","Notes(optional)":"Notes(optional)","Upload_documents(optional)":"Upload documents(optional)","Max_File_Size:_28.6MB":"Max File Size: 28.6MB","#_of_Service_Records":"# of Service Records","Total":"Total","Add_Service_Record":"Add Service Record","No_data_found,_create_reminders_to_see_visualizations_here.":"No data found, create reminders to see visualizations here.","No_data_found,_insert/select_some_data_to_see_visualizations_here.":"No data found, insert/select some data to see visualizations here.","Edit_Odometer_Record":"Edit Odometer Record","Import_Data_from_CSV":"Import Data from CSV","In_order_for_this_utility_to_function_properly,_your_CSV_file_MUST_be_formatted_exactly_like_the_provided_sample._Dates_must_be_supplied_in_a_string._Numbers_must_be_supplied_as_numbers_without_currency_formatting.":"In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample. Dates must be supplied in a string. Numbers must be supplied as numbers without currency formatting.","Failure_to_format_the_data_correctly_can_cause_data_corruption._Please_make_sure_you_make_a_copy_of_the_local_database_before_proceeding.":"Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.","Download_Sample":"Download Sample","Upload_CSV_File":"Upload CSV File","Import":"Import","Edit_Service_Record":"Edit Service Record","Date_service_was_performed":"Date service was performed","Odometer_reading_when_serviced":"Odometer reading when serviced","Description_of_item(s)_serviced(i.e._Oil_Change)":"Description of item(s) serviced(i.e. Oil Change)","Cost_of_the_service":"Cost of the service","Move_To":"Move To","#_of_Repair_Records":"# of Repair Records","Add_Repair_Record":"Add Repair Record","Add_New_Repair_Record":"Add New Repair Record","Date_repair_was_performed":"Date repair was performed","Odometer_reading_when_repaired":"Odometer reading when repaired","Description_of_item(s)_repaired(i.e._Alternator)":"Description of item(s) repaired(i.e. Alternator)","Cost_of_the_repair":"Cost of the repair","Choose_Supplies":"Choose Supplies","Add_Reminder":"Add Reminder","Select_Supplies":"Select Supplies","No_supplies_with_quantities_greater_than_0_is_found.":"No supplies with quantities greater than 0 is found.","Select":"Select","#_of_Upgrade_Records":"# of Upgrade Records","Add_Upgrade_Record":"Add Upgrade Record","Add_New_Upgrade_Record":"Add New Upgrade Record","Date_upgrade/mods_was_installed":"Date upgrade/mods was installed","Odometer_reading_when_upgraded/modded":"Odometer reading when upgraded/modded","Description_of_item(s)_upgraded/modded":"Description of item(s) upgraded/modded","Cost_of_the_upgrade/mods":"Cost of the upgrade/mods","#_of_Gas_Records":"# of Gas Records","Total_Fuel_Consumed":"Total Fuel Consumed","Total_Cost":"Total Cost","Add_Gas_Record":"Add Gas Record","Date_Refueled":"Date Refueled","Consumption":"Consumption","Fuel_Economy":"Fuel Economy","Unit_Cost":"Unit Cost","#_of_Supply_Records":"# of Supply Records","Add_Supply_Record":"Add Supply Record","Part_#":"Part #","Supplier":"Supplier","Quantity":"Quantity","Add_New_Supply_Record":"Add New Supply Record","Date_purchased":"Date purchased","Part_Number":"Part Number","Part_#/Model_#/SKU_#":"Part #/Model #/SKU #","Description_of_the_Part/Supplies":"Description of the Part/Supplies","Supplier/Vendor":"Supplier/Vendor","Part_Supplier":"Part Supplier","Edit_Supply_Record":"Edit Supply Record","Add_New_Service_Record":"Add New Service Record","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.":"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.","In_Stock":"In Stock","Edit_Repair_Record":"Edit Repair Record","Edit_Upgrade_Record":"Edit Upgrade Record","Save_Vehicle":"Save Vehicle","Add_New_Gas_Record":"Add New Gas Record","Date_refueled":"Date refueled","Odometer_Reading":"Odometer Reading","Odometer_reading_when_refueled":"Odometer reading when refueled","Fuel_Consumption":"Fuel Consumption","Amount_of_gas_refueled":"Amount of gas refueled","Is_Filled_To_Full":"Is Filled To Full","Missed_Fuel_Up(Skip_MPG_Calculation)":"Missed Fuel Up(Skip MPG Calculation)","Cost_of_gas_refueled":"Cost of gas refueled","Unit":"Unit","#_of_Tax_Records":"# of Tax Records","Add_Tax_Record":"Add Tax Record","Add_New_Tax_Record":"Add New Tax Record","Date_tax_was_paid":"Date tax was paid","Description_of_tax_paid(i.e._Registration)":"Description of tax paid(i.e. Registration)","Cost_of_tax_paid":"Cost of tax paid","Is_Recurring":"Is Recurring","Month":"Month","1_Month":"1 Month","3_Months":"3 Months","6_Months":"6 Months","1_Year":"1 Year","2_Years":"2 Years","3_Years":"3 Years","5_Years":"5 Years","Edit_Tax_Record":"Edit Tax Record","#_of_Notes":"# of Notes","Add_Note":"Add Note","Note":"Note","Add_New_Note":"Add New Note","Pinned":"Pinned","Description_of_the_note":"Description of the note","Min_Fuel_Economy":"Min Fuel Economy","Max_Fuel_Economy":"Max Fuel Economy","Edit_Gas_Record":"Edit Gas Record","#_of_Plan_Records":"# of Plan Records","Add_Plan_Record":"Add Plan Record","Planned":"Planned","Doing":"Doing","Testing":"Testing","Done":"Done","Add_New_Plan_Record":"Add New Plan Record","Describe_the_Plan":"Describe the Plan","Cost_of_the_Plan":"Cost of the Plan","Priority":"Priority","Critical":"Critical","Normal":"Normal","Low":"Low","Current_Stage":"Current Stage","#_of_Reminders":"# of Reminders","Urgency":"Urgency","Metric":"Metric","Add_New_Reminder":"Add New Reminder","Reminder_Description":"Reminder Description","Remind_me_on":"Remind me on","Future_Date":"Future Date","Future_Odometer_Reading":"Future Odometer Reading","Whichever_comes_first":"Whichever comes first","Other":"Other","Edit_Reminder":"Edit Reminder","Replace_picture(optional)":"Replace picture(optional)","Language":"Language","Manage_Languages":"Manage Languages","Upload":"Upload","Tokens":"Tokens","Generate_User_Token":"Generate User Token","Auto_Notify(via_Email)":"Auto Notify(via Email)","Token":"Token","Issued_To":"Issued To","Users":"Users","Email":"Email","Is_Admin":"Is Admin","An_error_has_occurred,_please_try_again_later":"An error has occurred, please try again later","Edit_Note":"Edit Note","Password":"Password","Remember_Me":"Remember Me","Login":"Login","Forgot_Password":"Forgot Password","Register":"Register","Request":"Request","I_Have_a_Token":"I Have a Token","Back_to_Login":"Back to Login","Email_Address":"Email Address","New_Password":"New Password","Reset_Password":"Reset Password","No_data_found_or_all_records_have_zero_sums,_insert_records_with_non-zero_sums_to_see_visualizations_here.":"No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.","Save_as_Template":"Save as Template","View_Templates":"View Templates","Select_Template":"Select Template","No_templates_are_found.":"No templates are found.","Use":"Use","Edit_Plan_Record":"Edit Plan Record","Date_Created":"Date Created","Last_Modified":"Last Modified","Shop_Supplies":"Shop Supplies","Uploaded_Documents":"Uploaded Documents","Upload_more_documents":"Upload more documents"} \ No newline at end of file +{"Garage":"Garage","Settings":"Settings","Admin_Panel":"Admin Panel","Logout":"Logout","Dark_Mode":"Dark Mode","Enable_CSV_Imports":"Enable CSV Imports","Use_Imperial_Calculation_for_Fuel_Economy_Calculations(MPG)":"Use Imperial Calculation for Fuel Economy Calculations(MPG)","This_Will_Also_Change_Units_to_Miles_and_Gallons":"This Will Also Change Units to Miles and Gallons","Use_UK_MPG_Calculation":"Use UK MPG Calculation","Input_Gas_Consumption_in_Liters,_it_will_be_converted_to_UK_Gals_for_MPG_Calculation":"Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation","Sort_lists_in_Descending_Order(Newest_to_Oldest)":"Sort lists in Descending Order(Newest to Oldest)","Replace_$0.00_Costs_with_---":"Replace $0.00 Costs with ---","Use_Three_Decimals_For_Fuel_Cost":"Use Three Decimals For Fuel Cost","Display_Saved_Notes_in_Markdown":"Display Saved Notes in Markdown","Auto_Refresh_Lapsed_Recurring_Reminders":"Auto Refresh Lapsed Recurring Reminders","Auto_Insert_Odometer_Records":"Auto Insert Odometer Records","Only_when_Adding_Service/Repair/Upgrade/Fuel_Record_or_Completing_a_Plan":"Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan","Enable_Authentication":"Enable Authentication","Visible_Tabs":"Visible Tabs","Service_Records":"Service Records","Dashboard":"Dashboard","Repairs":"Repairs","Upgrades":"Upgrades","Fuel":"Fuel","Odometer":"Odometer","Taxes":"Taxes","Notes":"Notes","Reminder":"Reminder","Supplies":"Supplies","Planner":"Planner","Default_Tab":"Default Tab","Service_Record":"Service Record","Tax":"Tax","Reminders":"Reminders","Backups":"Backups","Make":"Make","Restore":"Restore","About":"About","Add_New_Vehicle":"Add New Vehicle","Year":"Year","Year(must_be_after_1900)":"Year(must be after 1900)","Model":"Model","License_Plate":"License Plate","Electric_Vehicle":"Electric Vehicle","Use_Engine_Hours":"Use Engine Hours","Tags(optional)":"Tags(optional)","Upload_a_picture(optional)":"Upload a picture(optional)","Cancel":"Cancel","Edit_Vehicle":"Edit Vehicle","Delete_Vehicle":"Delete Vehicle","Manage_Vehicle":"Manage Vehicle","Expenses_by_Type":"Expenses by Type","Service":"Service","Expenses_by_Month":"Expenses by Month","As_of_Today":"As of Today","\u002B30_Days":"\u002B30 Days","\u002B60_Days":"\u002B60 Days","\u002B90_Days":"\u002B90 Days","Not_Urgent":"Not Urgent","Urgent":"Urgent","Very_Urgent":"Very Urgent","Past_Due":"Past Due","Reminders_by_Category":"Reminders by Category","Reminders_by_Urgency":"Reminders by Urgency","Collaborators":"Collaborators","Username":"Username","Delete":"Delete","Fuel_Mileage_by_Month":"Fuel Mileage by Month","Vehicle_Maintenance_Report":"Vehicle Maintenance Report","Export_Attachments":"Export Attachments","Gasoline":"Gasoline","Last_Reported_Odometer_Reading":"Last Reported Odometer Reading","Average_Fuel_Economy":"Average Fuel Economy","Total_Spent(excl._fuel)":"Total Spent(excl. fuel)","Total_Spent_on_Fuel":"Total Spent on Fuel","Type":"Type","Date":"Date","Description":"Description","Cost":"Cost","Repair":"Repair","Upgrade":"Upgrade","#_of_Odometer_Records":"# of Odometer Records","Add_Odometer_Record":"Add Odometer Record","Import_via_CSV":"Import via CSV","Export_to_CSV":"Export to CSV","Print":"Print","Add_New_Odometer_Record":"Add New Odometer Record","Date_recorded":"Date recorded","Odometer_reading":"Odometer reading","Notes(optional)":"Notes(optional)","Upload_documents(optional)":"Upload documents(optional)","Max_File_Size:_28.6MB":"Max File Size: 28.6MB","#_of_Service_Records":"# of Service Records","Total":"Total","Add_Service_Record":"Add Service Record","No_data_found,_create_reminders_to_see_visualizations_here.":"No data found, create reminders to see visualizations here.","No_data_found,_insert/select_some_data_to_see_visualizations_here.":"No data found, insert/select some data to see visualizations here.","Edit_Odometer_Record":"Edit Odometer Record","Import_Data_from_CSV":"Import Data from CSV","In_order_for_this_utility_to_function_properly,_your_CSV_file_MUST_be_formatted_exactly_like_the_provided_sample._Dates_must_be_supplied_in_a_string._Numbers_must_be_supplied_as_numbers_without_currency_formatting.":"In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample. Dates must be supplied in a string. Numbers must be supplied as numbers without currency formatting.","Failure_to_format_the_data_correctly_can_cause_data_corruption._Please_make_sure_you_make_a_copy_of_the_local_database_before_proceeding.":"Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.","Download_Sample":"Download Sample","Upload_CSV_File":"Upload CSV File","Import":"Import","Edit_Service_Record":"Edit Service Record","Date_service_was_performed":"Date service was performed","Odometer_reading_when_serviced":"Odometer reading when serviced","Description_of_item(s)_serviced(i.e._Oil_Change)":"Description of item(s) serviced(i.e. Oil Change)","Cost_of_the_service":"Cost of the service","Move_To":"Move To","#_of_Repair_Records":"# of Repair Records","Add_Repair_Record":"Add Repair Record","Add_New_Repair_Record":"Add New Repair Record","Date_repair_was_performed":"Date repair was performed","Odometer_reading_when_repaired":"Odometer reading when repaired","Description_of_item(s)_repaired(i.e._Alternator)":"Description of item(s) repaired(i.e. Alternator)","Cost_of_the_repair":"Cost of the repair","Choose_Supplies":"Choose Supplies","Add_Reminder":"Add Reminder","Select_Supplies":"Select Supplies","No_supplies_with_quantities_greater_than_0_is_found.":"No supplies with quantities greater than 0 is found.","Select":"Select","#_of_Upgrade_Records":"# of Upgrade Records","Add_Upgrade_Record":"Add Upgrade Record","Add_New_Upgrade_Record":"Add New Upgrade Record","Date_upgrade/mods_was_installed":"Date upgrade/mods was installed","Odometer_reading_when_upgraded/modded":"Odometer reading when upgraded/modded","Description_of_item(s)_upgraded/modded":"Description of item(s) upgraded/modded","Cost_of_the_upgrade/mods":"Cost of the upgrade/mods","#_of_Gas_Records":"# of Gas Records","Total_Fuel_Consumed":"Total Fuel Consumed","Total_Cost":"Total Cost","Add_Gas_Record":"Add Gas Record","Date_Refueled":"Date Refueled","Consumption":"Consumption","Fuel_Economy":"Fuel Economy","Unit_Cost":"Unit Cost","#_of_Supply_Records":"# of Supply Records","Add_Supply_Record":"Add Supply Record","Part_#":"Part #","Supplier":"Supplier","Quantity":"Quantity","Add_New_Supply_Record":"Add New Supply Record","Date_purchased":"Date purchased","Part_Number":"Part Number","Part_#/Model_#/SKU_#":"Part #/Model #/SKU #","Description_of_the_Part/Supplies":"Description of the Part/Supplies","Supplier/Vendor":"Supplier/Vendor","Part_Supplier":"Part Supplier","Edit_Supply_Record":"Edit Supply Record","Add_New_Service_Record":"Add New Service Record","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.":"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.","In_Stock":"In Stock","Edit_Repair_Record":"Edit Repair Record","Edit_Upgrade_Record":"Edit Upgrade Record","Save_Vehicle":"Save Vehicle","Add_New_Gas_Record":"Add New Gas Record","Date_refueled":"Date refueled","Odometer_Reading":"Odometer Reading","Odometer_reading_when_refueled":"Odometer reading when refueled","Fuel_Consumption":"Fuel Consumption","Amount_of_gas_refueled":"Amount of gas refueled","Is_Filled_To_Full":"Is Filled To Full","Missed_Fuel_Up(Skip_MPG_Calculation)":"Missed Fuel Up(Skip MPG Calculation)","Cost_of_gas_refueled":"Cost of gas refueled","Unit":"Unit","#_of_Tax_Records":"# of Tax Records","Add_Tax_Record":"Add Tax Record","Add_New_Tax_Record":"Add New Tax Record","Date_tax_was_paid":"Date tax was paid","Description_of_tax_paid(i.e._Registration)":"Description of tax paid(i.e. Registration)","Cost_of_tax_paid":"Cost of tax paid","Is_Recurring":"Is Recurring","Month":"Month","1_Month":"1 Month","3_Months":"3 Months","6_Months":"6 Months","1_Year":"1 Year","2_Years":"2 Years","3_Years":"3 Years","5_Years":"5 Years","Edit_Tax_Record":"Edit Tax Record","#_of_Notes":"# of Notes","Add_Note":"Add Note","Note":"Note","Add_New_Note":"Add New Note","Pinned":"Pinned","Description_of_the_note":"Description of the note","Min_Fuel_Economy":"Min Fuel Economy","Max_Fuel_Economy":"Max Fuel Economy","Edit_Gas_Record":"Edit Gas Record","#_of_Plan_Records":"# of Plan Records","Add_Plan_Record":"Add Plan Record","Planned":"Planned","Doing":"Doing","Testing":"Testing","Done":"Done","Add_New_Plan_Record":"Add New Plan Record","Describe_the_Plan":"Describe the Plan","Cost_of_the_Plan":"Cost of the Plan","Priority":"Priority","Critical":"Critical","Normal":"Normal","Low":"Low","Current_Stage":"Current Stage","#_of_Reminders":"# of Reminders","Urgency":"Urgency","Metric":"Metric","Add_New_Reminder":"Add New Reminder","Reminder_Description":"Reminder Description","Remind_me_on":"Remind me on","Future_Date":"Future Date","Future_Odometer_Reading":"Future Odometer Reading","Whichever_comes_first":"Whichever comes first","Other":"Other","Edit_Reminder":"Edit Reminder","Replace_picture(optional)":"Replace picture(optional)","Language":"Language","Manage_Languages":"Manage Languages","Upload":"Upload","Tokens":"Tokens","Generate_User_Token":"Generate User Token","Auto_Notify(via_Email)":"Auto Notify(via Email)","Token":"Token","Issued_To":"Issued To","Users":"Users","Email":"Email","Is_Admin":"Is Admin","An_error_has_occurred,_please_try_again_later":"An error has occurred, please try again later","Edit_Note":"Edit Note","Password":"Password","Remember_Me":"Remember Me","Login":"Login","Forgot_Password":"Forgot Password","Register":"Register","Request":"Request","I_Have_a_Token":"I Have a Token","Back_to_Login":"Back to Login","Email_Address":"Email Address","New_Password":"New Password","Reset_Password":"Reset Password","No_data_found_or_all_records_have_zero_sums,_insert_records_with_non-zero_sums_to_see_visualizations_here.":"No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.","Save_as_Template":"Save as Template","View_Templates":"View Templates","Select_Template":"Select Template","No_templates_are_found.":"No templates are found.","Use":"Use","Edit_Plan_Record":"Edit Plan Record","Date_Created":"Date Created","Last_Modified":"Last Modified","Shop_Supplies":"Shop Supplies","Uploaded_Documents":"Uploaded Documents","Upload_more_documents":"Upload more documents","Database_Migration":"Database Migration","Instructions":"Instructions","To_Postgres":"To Postgres","From_Postgres":"From Postgres","Import_To_Postgres":"Import To Postgres","Export_From_Postgres":"Export From Postgres"} \ No newline at end of file