Compare commits

...

1592 Commits
v1.0.3 ... main

Author SHA1 Message Date
Hargata Softworks
b184a1664b
Merge pull request #1167 from hargata/Hargata/all.api.endpoints
add endpoints to retrieve all records from all vehicles the user has …
2025-11-30 11:05:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1963fb8134 add endpoints to retrieve all records from all vehicles the user has access to 2025-11-30 11:01:34 -07:00
Hargata Softworks
1d5aeada6d
Merge pull request #1166 from hargata/Hargata/supply.api
add API endpoints for supplies
2025-11-30 09:21:20 -07:00
DESKTOP-T0O5CDB\DESK-555BD
64f25ddcb2 additional patches for access control 2025-11-30 09:19:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f5efe96856 fix error message 2025-11-30 09:10:09 -07:00
DESKTOP-T0O5CDB\DESK-555BD
bf8bc6a119 add API endpoints for supplies 2025-11-30 09:05:27 -07:00
Hargata Softworks
752eb30dc4
Merge pull request #1159 from hargata/Hargata/modal.standardization
standardized modal headers across the board
2025-11-20 09:44:04 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7c2f626647 standardized modal headers across the board 2025-11-20 09:42:15 -07:00
Hargata Softworks
5122bb586b
Merge pull request #1157 from hargata/Hargata/context.order
higher quality favicon
2025-11-17 12:17:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8dffb3d7c6 higher quality favicon 2025-11-17 12:16:20 -07:00
Hargata Softworks
fff013497a
Merge pull request #1156 from hargata/Hargata/context.order
quick nav in garage context now respects tab order.
2025-11-17 10:40:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
bc10c095a7 quick nav in garage context now respects tab order. 2025-11-17 10:40:04 -07:00
Hargata Softworks
7d203d4175
Merge pull request #1154 from hargata/Hargata/565
overlooked operation response
2025-11-15 09:33:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2eeda9c65 overlooked operation response 2025-11-15 09:32:48 -07:00
Hargata Softworks
3f5e3a4b97
Merge pull request #1153 from hargata/Hargata/565
allow users to leave households
2025-11-14 09:59:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ab967f1f52 allow users to leave households 2025-11-14 09:46:33 -07:00
Hargata Softworks
2f95e0445c
Merge pull request #1152 from hargata/Hargata/565
fix API endpoints
2025-11-13 16:53:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c3110176f2 fix API endpoints 2025-11-13 16:50:59 -07:00
Hargata Softworks
e81c0616f7
Merge pull request #1151 from hargata/Hargata/565
fixed order.
2025-11-13 14:39:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0aae215f08 fixed order. 2025-11-13 14:33:10 -07:00
Hargata Softworks
0f2a7c372b
Merge pull request #1150 from hargata/Hargata/565
rbac lol
2025-11-13 14:26:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1032112885 rbac lol 2025-11-13 14:24:50 -07:00
Hargata Softworks
d23b16da67
Merge pull request #1149 from hargata/Hargata/565
allow for multiple households.
2025-11-13 11:10:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5bb0170011 allow for multiple households. 2025-11-13 11:07:46 -07:00
Hargata Softworks
07d1f5cd86
Merge pull request #1146 from hargata/Hargata/565
add permissions at household level
2025-11-13 10:21:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
61bbfb8b1e ui fix 2025-11-13 09:40:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1a805ada8e Updated to use OperationResponse instead of generic json. 2025-11-13 09:13:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6065c14f3b add role check in bulk actions. 2025-11-13 07:42:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
76d9be7ee5 update permission 2025-11-12 21:12:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5f6b77648d add permissions at household level 2025-11-12 21:07:06 -07:00
Hargata Softworks
d09d10a5b3
Merge pull request #1134 from hargata/Hargata/1133
Handle image attachments that are too long to fit in a single page.
2025-11-12 13:18:18 -07:00
Hargata Softworks
98561e7f00
Merge pull request #1139 from hargata/Hargata/136
Hargata/136 - User Households
2025-11-12 09:15:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
780856433d z 2025-11-12 09:14:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5c895b4f82 some code cleanup 2025-11-12 09:03:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ec52bc09e4 allow admins to manage households for all users 2025-11-11 22:23:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c0cfd74fc6 reload garage regardless of error 2025-11-11 17:21:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
066e5ca648 household members cannot delete vehicles if they are not direct collaborators 2025-11-11 16:48:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e30f1def1e front end for managing households 2025-11-11 16:11:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a137fbaa52 front end for managing households 2025-11-11 16:04:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b98585006b User Households 2025-11-11 14:45:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d9c5c7237 add confirmation field for admin password. 2025-11-10 08:18:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d4f06b96ff add exception logging for JWT validation failure 2025-11-10 08:00:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9f14cd0a5e bump version 2025-11-09 13:10:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d8047b07c5 Handle image attachments that are too long to fit in a single page. 2025-11-09 12:10:27 -07:00
Hargata Softworks
b42527f15d
Merge pull request #1132 from hargata/Hargata/ins.label.bug
add translation key
2025-11-07 13:54:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cb432d92a6 add translation key 2025-11-07 13:27:29 -07:00
Hargata Softworks
300edd8c4e
Merge pull request #1131 from hargata/Hargata/ins.label.bug
fix template selector modal not hiding after creating inspection temp…
2025-11-07 12:53:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cb77d6675e fix template selector modal not hiding after creating inspection template 2025-11-07 12:52:44 -07:00
Hargata Softworks
d29b20b16b
Merge pull request #1130 from hargata/Hargata/ins.label.bug
Fix label bug
2025-11-07 12:13:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f3999650bd Fix label bug 2025-11-07 12:13:32 -07:00
Hargata Softworks
bd47ef41f0
Merge pull request #1129 from hargata/Hargata/copy.attachment.link
Allow users to copy link to attachments
2025-11-07 10:24:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0c4ccea9ec clean up 2025-11-07 10:24:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
18243ec779 Allow users to copy link to attachments 2025-11-07 10:21:34 -07:00
Hargata Softworks
83f1a25057
Merge pull request #1127 from hargata/Hargata/dupe.inspection.template
add functionality to duplicate inspection record templates
2025-11-06 16:29:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7f671c1802 call back 2025-11-06 16:29:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f05f06824 add functionality to duplicate inspection record templates 2025-11-06 16:27:53 -07:00
Hargata Softworks
2ab822af8f
Merge pull request #1126 from hargata/Hargata/fix.reminder.bug
fix UI bug when creating reminder record from other records.
2025-11-05 12:37:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0cef3c9044 fix UI bug when creating reminder record from other records. 2025-11-05 12:36:33 -07:00
Hargata Softworks
9ef2124c8a
Merge pull request #1125 from hargata/Hargata/1119
add GET API Endpoint to return all extrafields
2025-11-05 11:06:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fd9d57c3db add documentation 2025-11-05 11:04:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a3e0da1ebb add GET API Endpoint to return all extrafields 2025-11-05 10:33:48 -07:00
Hargata Softworks
ebca12d82a
Merge pull request #1124 from hargata/Hargata/add.api
API Enhancements
2025-11-05 09:11:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9b00933aa8 fix action item progress 2025-11-05 09:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
98ef7d455b Add check to ensure that reminder for inspection still exists. Add recordId to response object returned from API 2025-11-05 07:52:43 -07:00
Hargata Softworks
7062595d4d
Merge pull request #1123 from hargata/Hargata/979.fix
allow users to upload files after inspection is created.
2025-11-04 18:10:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ae19b64327 allow users to upload files after inspection is created. 2025-11-04 18:10:28 -07:00
Hargata Softworks
4d3d624ac8
Merge pull request #1122 from hargata/Hargata/979.fix
minor UI bug fixes
2025-11-04 16:16:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ef0f4fa5f8 minor UI bug fixes 2025-11-04 16:15:26 -07:00
Hargata Softworks
3d33a798f1
Merge pull request #1105 from hargata/Hargata/cost.data.fix
Version 1.5.4 Changes
2025-11-04 15:47:06 -07:00
Hargata Softworks
b8a568b3e8
Merge pull request #1121 from hargata/Hargata/979
weird bug
2025-11-04 15:45:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
190b7b4e23 weird bug 2025-11-04 15:40:31 -07:00
Hargata Softworks
bca2393601
Merge pull request #1120 from hargata/Hargata/979
fix bug with postgres migration
2025-11-04 15:36:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5ad1ae3d5a fix bug with postgres migration 2025-11-04 15:32:08 -07:00
Hargata Softworks
123a6f2dd4
Merge pull request #1118 from hargata/Hargata/979
Hargata/979
2025-11-04 15:14:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3de2ecc096 add functionality to duplicate inspection record templates to other vehicles. 2025-11-04 15:04:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9a504a3e53 add functionality for users to edit tags. 2025-11-04 13:37:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a90b8bdf93 add method to write to temp 2025-11-04 13:04:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
50f4308417 add security check to pushing back recurring reminders 2025-11-04 12:32:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c5ea6c01df allow users to move fields up and down 2025-11-04 12:23:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1e32d57d16 add linked record as attachments 2025-11-04 08:38:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
350cdcb03a add linking to other records 2025-11-04 08:09:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
aedaccfeda allow inspections to be searched 2025-11-03 12:20:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
044cf3bf38 add, view and delete inspection record. 2025-11-03 10:42:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
dd30e7b8fa add inspection record 2025-11-02 22:25:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e802b8c3f9 add functionality to delete inspection templates 2025-11-01 09:34:22 -06:00
DESKTOP-T0O5CDB\DESK-555BD
042985df54 inspection record template editor 2025-10-31 20:00:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
20365a549a inspection record tab 2025-10-31 15:59:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5a67217843 inspection record data access. 2025-10-31 13:02:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b17c8e3bc5 tweak tab order and register inspection data access 2025-10-31 11:59:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3377aa15b5 backend for saving inspection record template 2025-10-31 11:25:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a38ff16d79 object update 2025-10-31 10:30:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
772b778c98 show inspection tab on front end 2025-10-31 10:01:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3fcc1700f inspection objects 2025-10-31 09:36:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f358d20ad0 object for inspection record template 2025-10-30 17:07:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
329c8a403a 403 error page 2025-10-29 11:15:57 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d709969a93 add tags filter to Reminder APIs 2025-10-29 11:05:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
63178b6bdb load custom widgets on dashboard load and bump version 2025-10-26 10:25:33 -06:00
DESKTOP-T0O5CDB\DESK-555BD
129cdf4554 Update dependencies 2025-10-18 18:24:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7bfa00d6a1 Updated Error Page for 403 errors 2025-10-16 15:08:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
342ac6e560 fix coords upload 2025-10-15 12:21:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7cb7777060 fix for when there is exactly 5 years of data 2025-10-13 14:25:24 -06:00
Hargata Softworks
b3283fc3e8
Merge pull request #1102 from hargata/Hargata/garage.quick.nav
allow users to quickly navigate to specific tab in vehicle page from …
2025-10-13 07:22:19 -06:00
DESKTOP-T0O5CDB\DESK-555BD
535f0ad677 allow users to quickly navigate to specific tab in vehicle page from garage 2025-10-13 07:21:06 -06:00
Hargata Softworks
e3da3215ca
Merge pull request #1100 from hargata/Hargata/contextmenufix
fixes positioning of the context menu
2025-10-09 22:06:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
99a34a888e fixes positioning of the context menu 2025-10-09 22:05:41 -06:00
Hargata Softworks
a13cf0733b
Merge pull request #1099 from hargata/Hargata/1098
paginated cost table
2025-10-09 21:44:49 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fc87bd3b9c paginated cost table 2025-10-09 19:11:17 -06:00
Hargata Softworks
b697501e59
Merge pull request #1097 from hargata/Hargata/1093
translation request limit workaround
2025-10-08 23:05:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c7974b412f translation request limit workaround 2025-10-08 23:04:42 -06:00
Hargata Softworks
7589d139cb
Merge pull request #1096 from hargata/Hargata/1093
fix translation casing
2025-10-08 21:11:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8a2ab47e49 fix translation casing 2025-10-08 21:10:52 -06:00
Hargata Softworks
08e0ff2798
Merge pull request #1095 from hargata/Hargata/1093
Hargata/1093
2025-10-08 21:03:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fd102224fc consistent casing 2025-10-08 21:01:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d71e8ddcf2 hide https cert password 2025-10-08 20:59:29 -06:00
Hargata Softworks
96291bae76
Merge pull request #1094 from hargata/Hargata/1093
fixed intervals for certain reminders
2025-10-08 20:21:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
643f7a3f79 fixed intervals for certain reminders 2025-10-08 20:20:03 -06:00
Hargata Softworks
1893a5985f
Merge pull request #1092 from hargata/Hargata/oidc.jwks
add a JWKS endpoint to validate token signing keys
2025-10-08 16:03:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
487ce0a42a add a JWKS endpoint to validate token signing keys 2025-10-08 15:57:25 -06:00
Hargata Softworks
505bc5b3c6
Merge pull request #1089 from hargata/Hargata/server.config.endpoints
Allow users to configure HTTPS and HTTP endpoints entirely within the…
2025-10-08 12:09:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8ddd04017b Allow users to configure HTTPS and HTTP endpoints entirely within the server configurator 2025-10-08 11:54:06 -06:00
Hargata Softworks
fce40ae9be
Merge pull request #1088 from hargata/Hargata/garage.select.mobile
make collaborator management more workable on mobile devices.
2025-10-07 15:14:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a59f6b9e86 make collaborator management more workable on mobile devices. 2025-10-07 15:13:29 -06:00
Hargata Softworks
3bbe9538a7
Merge pull request #1087 from hargata/Hargata/garage.select.mobile
user select none
2025-10-07 14:32:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ba886d8b08 user select none 2025-10-07 14:31:55 -06:00
Hargata Softworks
0bfef450ed
Merge pull request #1086 from hargata/Hargata/garage.select.mobile
add context menu to mobile devices.
2025-10-07 14:19:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dcf30d73b7 add context menu to mobile devices. 2025-10-07 14:18:21 -06:00
Hargata Softworks
5f0b7b9bb1
Merge pull request #1076 from hargata/Hargata/1075
stopgap measure for handling PDF attachments
2025-10-06 17:30:46 -06:00
DESKTOP-T0O5CDB\DESK-555BD
831f256872 save custom widgets acknowledgement 2025-10-06 17:29:02 -06:00
Hargata Softworks
1e4acf2301
Merge pull request #1081 from hargata/Hargata/garage.select
add context menu to garage items
2025-10-06 14:33:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
68826f0924 allow users to bulk manage collaborators 2025-10-06 14:23:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9ac7f78695 Merge branch 'Hargata/1075' into Hargata/garage.select 2025-10-03 22:24:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
22d9936b24 clean up docker compose 2025-10-03 22:21:48 -06:00
DESKTOP-T0O5CDB\DESK-555BD
770cde977d viewmodel for vehicles 2025-10-03 22:16:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f6924c83bf lol 2025-10-03 16:03:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
640b41d4ab Merge branch 'Hargata/1075' into Hargata/garage.select 2025-10-03 15:51:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c2173aa12c removed .env file because it's superseded by the server settings configurator 2025-10-03 15:35:35 -06:00
Hargata Softworks
f4745c6dce
Merge pull request #1084 from hargata/hargata-patch-1
Update issue templates
2025-10-03 15:34:28 -06:00
Hargata Softworks
aa64ce4194
Update issue templates 2025-10-03 13:43:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
18f086de32 added delete function and backend for managing collaborators 2025-09-26 17:04:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
59b58a836c sort via context menu 2025-09-26 15:40:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6042b3dedb add context menu to garage items 2025-09-26 12:11:19 -06:00
Hargata Softworks
92b5840073
Merge pull request #1079 from hargata/Hargata/nav.overhaul
Add navigatable tabs.
2025-09-25 12:52:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
55b52dcfa0 Add navigatable tabs. 2025-09-25 12:36:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
98ff9eb9a6 stopgap measure until we figure out a solution for embedding inline-PDFs. 2025-09-22 08:05:39 -06:00
Hargata Softworks
95eab55d02
Merge pull request #1071 from hargata/Hargata/make.search.optional
make garage search optional
2025-09-17 14:56:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9dccce7208 trim trailing comma 2025-09-17 14:55:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3ba8f3f297 make garage search optional 2025-09-17 14:54:11 -06:00
Hargata Softworks
08fdd5ee05
Merge pull request #1060 from hargata/Hargata/1059
add case insensitive search to garage
2025-09-17 14:22:49 -06:00
Hargata Softworks
fefe61f27e
Merge pull request #1070 from hargata/Hargata/951
Hargata/951
2025-09-17 14:03:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7844d2430d front end for updating cookie life span. 2025-09-17 14:01:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
df0e686615 backend for managing cookie life span. 2025-09-17 13:37:07 -06:00
Hargata Softworks
969b28f1b4
Merge pull request #1069 from hargata/Hargata/image.map.update
Hargata/image.map.update
2025-09-17 08:47:19 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0922453dfb only render circles if we have exactly three points 2025-09-17 08:34:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
931ca855d2 add circle shape type. 2025-09-17 08:30:59 -06:00
Hargata Softworks
59868f2463
Merge pull request #1068 from Zeromark30/patch-1
Update _VehicleImageMap.cshtml
2025-09-17 08:20:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6c0565102a add shape to imagemap 2025-09-17 08:19:52 -06:00
Hargata Softworks
a7886bfdc4
Merge pull request #1067 from Jekotia/patch-1
Update docker-compose.traefik.yml
2025-09-17 08:13:27 -06:00
Zeromark30
6c069dde17
Update _VehicleImageMap.cshtml
Add the option for circles in VehicleImageMap using coordinate system from  https://www.image-map.net/
2025-09-16 22:04:01 -04:00
Jekotia
950fc2af48
Update docker-compose.traefik.yml
Fix errors in Traefik example
2025-09-16 12:27:28 -04:00
DESKTOP-T0O5CDB\DESK-555BD
53937c9030 add attachment preview for images. 2025-09-16 08:55:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e12895f246 add search icon. 2025-09-15 15:02:05 -06:00
Hargata Softworks
55328d1be4
Merge pull request #1066 from hargata/Hargata/download.file.attachment
bump version
2025-09-15 14:20:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8f589d7638 bump version 2025-09-15 14:19:33 -06:00
Hargata Softworks
31671fa137
Merge pull request #1065 from hargata/Hargata/download.file.attachment
Hargata/download.file.attachment
2025-09-15 14:14:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5859a020bd force file downloads if attachment is not a link instead of opening everything in new tab. 2025-09-15 14:13:15 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9f556f9c95 add case insensitive search to garage 2025-09-12 09:32:48 -06:00
Hargata Softworks
6aad531099
Merge pull request #1040 from hargata/Hargata/1039
use culture invariant when converting opacity to string.
2025-09-09 09:53:23 -07:00
Hargata Softworks
10b420cd81
Merge pull request #1053 from hargata/Hargata/1050
URL Attachments now export separately.
2025-09-08 08:03:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5f6f713ace URL Attachments now export separately. 2025-09-08 09:01:51 -06:00
Hargata Softworks
e490117b23
Merge pull request #1048 from hargata/Hargata/1035
persist global search settings in localStorage.
2025-08-27 15:31:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
653fd47621 persist global search settings in localStorage. 2025-08-27 15:30:19 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e9eb86ae3f use culture invariant when converting opacity to string. 2025-08-23 17:49:40 -06:00
Hargata Softworks
31690bc8df
Merge pull request #1032 from hargata/Hargata/929
add datetime format override
2025-08-20 09:04:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
18f9129ecb add datetime format override 2025-08-20 09:02:55 -06:00
Hargata Softworks
939426a451
Merge pull request #1027 from hargata/Hargata/reminder.param
Update Dependencies
2025-08-18 11:19:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ba621a7cf7 Update JSON Web Token Dependency for OIDC 2025-08-18 11:17:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c6f1b8ab6e bump dependencies 2025-08-18 11:07:32 -06:00
Hargata Softworks
c3c9ed2c04
Merge pull request #1026 from hargata/Hargata/reminder.param
added urgencies param to reminder GET API
2025-08-18 10:24:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ed8da2a264 fix CSV import 2025-08-18 10:23:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c0f336d197 added urgencies param to reminder GET API 2025-08-18 10:19:30 -06:00
Hargata Softworks
32b14c17b4
Merge pull request #1008 from hargata/Hargata/1005
Version 1.5.0 Changes
2025-08-11 14:08:59 -06:00
Hargata Softworks
bf25c4122a
Merge pull request #1019 from hargata/Hargata/929
update vehicle map into a switch
2025-08-06 16:38:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dc83d5c518 update vehicle map into a switch 2025-08-06 16:37:00 -06:00
Hargata Softworks
3bd246db3a
Merge pull request #1018 from hargata/Hargata/929
provide locale samples
2025-08-06 12:06:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
da8cde234a provide locale samples 2025-08-06 12:05:45 -06:00
Hargata Softworks
4c16ab80e9
Merge pull request #1017 from hargata/Hargata/929
Add Locale Override specifically for LubeLogger
2025-08-06 11:19:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a7df12c88b Add app-specific locale override 2025-08-06 11:15:24 -06:00
Hargata Softworks
4abcc5c1e5
Merge pull request #1015 from hargata/Hargata/38
Allow users to upload a map of the vehicle
2025-08-05 14:34:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6fe4d5d84b bump version 2025-08-05 11:15:49 -06:00
DESKTOP-T0O5CDB\DESK-555BD
19ddc34d59 allow users to define opacity. 2025-08-05 10:59:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0c7b6ddf2b front end for loading map search results 2025-08-05 10:48:51 -06:00
Hargata Softworks
97914da6ad
Merge pull request #1013 from hargata/Hargata/1012
Skip UK Conversion for Electric Vehicles
2025-08-04 21:38:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1e7401b263 See #38 2025-08-04 21:37:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
56fbcd1892 Skip UK Conversion for Electric Vehicle since a kWh in the UK is the same as a kWh everywhere else. 2025-08-04 09:01:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
bd72e04e21 fixed bug where columns don't show up if user doesn't have enable CSV imports enabled. 2025-07-22 21:17:18 -06:00
Hargata Softworks
4dca661c02
Merge pull request #998 from hargata/Hargata/api.hot.fix
Hargata/api.hot.fix
2025-07-09 10:26:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4731615ac5 bump version 2025-07-09 10:25:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2d88db2e68 minor patch for the API 2025-07-09 10:24:45 -06:00
Hargata Softworks
4d5bbc39dd
Update screenshots.md 2025-07-02 15:08:52 -06:00
Hargata Softworks
3618cc6456
Merge pull request #989 from hargata/Hargata/updated.website
fixed width
2025-07-01 09:27:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7ebda7eafc fixed width 2025-07-01 09:26:45 -06:00
Hargata Softworks
24e76eb7c0
Merge pull request #988 from hargata/Hargata/updated.website
fix mobile view.
2025-06-30 20:51:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
67f36a2940 fix mobile view. 2025-06-30 20:51:40 -06:00
Hargata Softworks
0753d04d47
Merge pull request #987 from hargata/Hargata/updated.website
Updated website.
2025-06-30 19:15:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7c59051168 Updated website. 2025-06-30 19:14:46 -06:00
Hargata Softworks
1108d3b822
Merge pull request #986 from hargata/Hargata/duedays.duedistance
Added due days and due distance columns to reminder.
2025-06-30 09:29:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9669e8fc65 Added due days and due distance columns to reminder. 2025-06-30 09:28:26 -06:00
Hargata Softworks
cd8d99ff89
Merge pull request #983 from hargata/Hargata/code.cleanup.3
Code Cleanup - Views
2025-06-28 10:39:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
98e8dfe373 cleaned up reminders 2025-06-28 10:36:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
24ebc39aa0 more clean up 2025-06-28 10:28:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9321edf6f6 clean up view for plan 2025-06-28 10:12:43 -06:00
DESKTOP-T0O5CDB\DESK-555BD
159df59b22 clean up view for odometer. 2025-06-28 10:05:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b32322cb65 Clean up views for Gas, Notes, and Repair 2025-06-28 10:01:37 -06:00
Hargata Softworks
56c4d0410b
Merge pull request #982 from hargata/Hargata/867
only show metrics that have data or the tab is visible.
2025-06-27 09:29:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dc46bc7eec only show metrics that have data or the tab is visible. 2025-06-27 09:28:20 -06:00
Hargata Softworks
4fd55ba0b8
Merge pull request #978 from hargata/Hargata/updated.layout
mpg chart grace revisited.
2025-06-25 09:44:19 -06:00
DESKTOP-T0O5CDB\DESK-555BD
340b9c9a00 mpg chart grace revisited. 2025-06-25 09:43:51 -06:00
Hargata Softworks
8daa4d7c5f
Merge pull request #976 from hargata/Hargata/updated.layout
let
2025-06-24 23:03:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5b148e47da let 2025-06-24 23:03:38 -06:00
Hargata Softworks
050e3ce355
Merge pull request #975 from hargata/Hargata/updated.layout
Fixed navbar on smaller screens
2025-06-24 22:51:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d24b28beb5 Fixed navbar on smaller screens 2025-06-24 22:50:43 -06:00
Hargata Softworks
36cfac402c
Merge pull request #974 from hargata/Hargata/updated.layout
Updated configurator
2025-06-24 14:43:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
eee4e2e56b config 2025-06-24 14:43:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8c770536c0 Updated configurator 2025-06-24 14:40:31 -06:00
Hargata Softworks
f0ad263306
Merge pull request #960 from hargata/Hargata/updated.layout
Hargata/updated.layout
2025-06-24 14:37:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c4adc5b297 iOS Safari WebKit Hacks. 2025-06-24 12:27:46 -06:00
DESKTOP-T0O5CDB\DESK-555BD
128154f4c5 fix remote auth debug page. 2025-06-24 11:12:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
39370a50cb more css 2025-06-24 11:06:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
71fed1ff7e add frost to navbar 2025-06-24 10:37:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3e5d8ce0a2 more css 2025-06-24 10:06:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6555ee25e2 fix tab related bug in garage 2025-06-23 22:21:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0d3059a176 fixed API and migration view. 2025-06-23 13:43:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d8cf3a049b fixed scrollbars in dashboard and settings page. 2025-06-23 09:33:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
055b183f2a Allow users to skip overwriting credential environment variables. 2025-06-21 09:32:00 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44dcd48a56 moved some stuff around in the settings tab 2025-06-21 09:05:57 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e61c1d4a70 code cleanup goes brrr 2025-06-21 08:17:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6d961e0540 more clean up 2025-06-20 13:29:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fbb2fb724d added method to test email config. 2025-06-20 12:54:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ef86337d78 updated translation. 2025-06-20 12:18:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0ff374389f Added Server Settings Configurator for Server Settings. 2025-06-20 12:15:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
94d9f45b13 allow serverConfigs to be backed up. 2025-06-19 15:44:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1d734e78a7 stylesheet for future. 2025-06-19 15:38:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d0f9149585 add backend to write server config. 2025-06-19 15:24:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9837f81b87 add serverconfig 2025-06-19 14:36:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
725638d217 add small logo url 2025-06-19 13:08:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b0a5cab2b6 Server Config 2025-06-19 09:05:56 -06:00
Hargata Softworks
ebaf55e3b0
Merge pull request #973 from hargata/Hargata/updated.uploader
cleaned up code.
2025-06-18 17:11:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
49870f13c9 more clean up. 2025-06-18 17:11:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a749de71e9 cleaned up code. 2025-06-18 17:06:10 -06:00
Hargata Softworks
36b80bcef5
Merge pull request #972 from hargata/Hargata/updated.uploader
Hargata/updated.uploader
2025-06-18 13:24:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
16f9740b6c allow users to attach a link as a document to a record. 2025-06-18 13:15:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2eab072e6d Add new updated file uploader. 2025-06-18 12:13:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
59c561ebba disable scroll on body when mobile nav is shown. 2025-06-06 11:47:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
07f993bec8 add case insensitive search 2025-06-06 10:58:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
bd6cb94ccd Added API Get Params for querying. 2025-06-06 09:34:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4206c02618 save selected tag to restore after saving record. 2025-06-03 08:49:12 -06:00
Hargata Softworks
b276fc02ce
Merge pull request #966 from hargata/Hargata/957
Add Extra Fields to Notes
2025-06-03 08:30:14 -05:00
DESKTOP-T0O5CDB\DESK-555BD
e558248f7c added column preferences for note record. 2025-06-03 07:25:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5a113a1636 front end for saving note extra fields. 2025-06-03 07:06:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
651dac3f27 add extra fields to notes. 2025-06-02 21:46:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4ecafbbdf7 Added UserMetric to the ReminderExportModel. 2025-06-02 21:32:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
29680cf0e9 made vehicle title the edit button. 2025-05-17 10:39:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
723eb1a769 Added API endpoint to check for latest release. 2025-05-17 10:14:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
732a628c20 Merge branch 'Hargata/148.changes' into Hargata/updated.layout 2025-05-17 09:49:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
84d40edb0e set graph grace see #941 2025-05-16 19:58:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9842f0e501 add report header. 2025-05-16 19:23:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44c5f921a5 Merge branch 'Hargata/908' into Hargata/updated.layout 2025-05-16 16:54:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fae4aa31aa show vehicle thumbnail 2025-05-16 14:45:06 -06:00
Hargata Softworks
adb505c87c
Merge pull request #950 from iamdabe/vehicle-home-thumbnail
Setting to show the vehicle thumbnail in the header
2025-05-16 14:31:45 -06:00
Hargata Softworks
d21b0a9e29
Merge branch 'Hargata/updated.layout' into vehicle-home-thumbnail 2025-05-16 14:31:30 -06:00
Hargata Softworks
15e4181aff
Merge pull request #949 from iamdabe/main
Updated _NoteModal.cshtml label to correctly target file input 'noteFiles'
2025-05-16 14:25:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3a4627ef02 use regular sized logo in mobile view in garage 2025-05-16 14:12:08 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d9e11273bd fix admin panel view 2025-05-16 13:49:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8e6f40a1b1 Updated layout for post hiatus release. 2025-05-16 13:44:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
55f86ecb3f add reportheader. 2025-05-16 11:11:10 -06:00
Dave Walker
8eef1465cf First commit for new setting to show the vehicle thumbnail in the header of each vehicle page. 2025-05-15 18:22:48 +02:00
iamdabe
fd0ffba7c4
Fixed _NoteModal.cshtml label to correctly target file input 'noteFiles' 2025-05-15 17:33:43 +02:00
DESKTOP-T0O5CDB\DESK-555BD
afb710e6ad Add server API version so that we can track the current version. 2025-04-30 09:51:04 -06:00
Hargata Softworks
419b755e7a
Merge pull request #937 from hargata/Hargata/update.configurator.2
Add Server Domain into configurator
2025-04-27 08:58:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8cd5342c02 Add Server Domain into configurator 2025-04-27 08:57:48 -06:00
Hargata Softworks
fa32ecdb5a
Merge pull request #916 from hargata/Hargata/oidc.userinfo
V1.4.7 Changes
2025-04-27 08:41:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d08726bd85 Add LUBELOGGER_DOMAIN environment variable which is used to craft registration and reset password links to reduce user friction. 2025-04-27 08:37:08 -06:00
Hargata Softworks
a87861069b
Merge pull request #936 from hargata/Hargata/877
Hargata/877 - API Endpoints for Basic Reminders
2025-04-26 09:35:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b6d6a8765d update API documentation 2025-04-26 09:32:15 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f307c0933a add delete endpoint 2025-04-26 09:16:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9968ccb541 add put API 2025-04-26 09:11:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5d3746f168 add POST api endpoint for basic reminders. 2025-04-26 08:56:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
df0c4eeca2 Merge branch 'Hargata/oidc.userinfo' into Hargata/877 2025-04-26 08:15:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5b3c0aed72 Add Extra Field Type to multi edit modals 2025-04-26 07:53:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
040728b96d Fix 931 2025-04-24 20:54:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e8b7b3e4ba optimized styling for planners tab 2025-04-16 11:16:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cf1f1a884b add tooltip for real long file names 2025-04-16 10:01:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c470db0590 minor UI fix on garage add. 2025-04-09 08:55:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cb71650adf API hardening, allow nulls for list types in API payload, will auto convert to empty list. 2025-04-08 08:15:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
923d59af0a update configurator 2025-04-05 08:26:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d68e1a3939 Add userinfo to retrieve email claim if not provided in id_token 2025-04-05 08:17:40 -06:00
Hargata Softworks
bf954a2946
Merge pull request #905 from hargata/Hargata/oidc.check.claims
Add debug page for advance OIDC debugging
2025-03-31 07:49:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d3f29be227 Add debug page for advance OIDC debugging 2025-03-31 07:48:29 -06:00
Hargata Softworks
5afe88a33a
Merge pull request #904 from hargata/Hargata/oidc.check.claims
add openid troubleshooting mode.
2025-03-30 20:40:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6937eff576 add troubleshooting mode. 2025-03-30 20:39:25 -06:00
Hargata Softworks
6371cccc48
Merge pull request #903 from hargata/Hargata/oidc.check.claims
set default oidc claims to openid email
2025-03-30 11:30:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fc174160e0 set default oidc claims to openid email 2025-03-30 11:28:29 -06:00
Hargata Softworks
876f99fd26
Merge pull request #901 from hargata/Hargata/oidc.check.claims
Add check for email claims.
2025-03-29 07:40:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
75c65b4681 Add check for email claims. 2025-03-29 07:37:09 -06:00
Hargata Softworks
76031d27d7
Merge pull request #900 from hargata/Hargata/612
Add extra field type.
2025-03-28 10:00:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4079a93c3e Add extra field type. 2025-03-28 09:40:13 -06:00
Hargata Softworks
d913ab2009
Merge pull request #899 from hargata/Hargata/update.deps
Hargata/update.deps
2025-03-27 06:58:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6bfbcc4374 More dependencies 2025-03-27 06:57:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
73d9a7e6e9 CHORE: Update Dependencies 2025-03-27 06:50:02 -06:00
Hargata Softworks
3cbce8b584
Merge pull request #898 from hargata/Hargata/879
Allow users to hide calendar tab.
2025-03-27 06:36:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e4fcb52b24 Allow users to hide calendar tab. 2025-03-27 06:34:57 -06:00
Hargata Softworks
ae4f01ac9a
Merge pull request #897 from hargata/Hargata/893
Allow users to hide Done and Delete columns.
2025-03-27 06:10:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
46b3845b3e Allow users to hide Done and Delete columns. 2025-03-27 06:06:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ffb126276f Add ID to reminderexportmodel. 2025-03-27 05:59:01 -06:00
Hargata Softworks
a9e4be823d
Merge pull request #896 from hargata/Hargata/895
Hargata/895
2025-03-27 05:56:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
56cae008a6 Fixed UI and odometer adjustments invariant formats. 2025-03-27 05:54:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c2dd379ea3 Fixed vehicle API not respecting locale invariant format. 2025-03-27 05:45:48 -06:00
Hargata Softworks
baa569b323
Merge pull request #894 from hargata/Hargata/893
added urgency and due metrics for recurring reminder selector
2025-03-26 11:51:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
64ce0f8c07 added urgency and due metrics for recurring reminder selector 2025-03-26 11:33:00 -06:00
Hargata Softworks
bd98e5a6cd
Merge pull request #885 from hargata/Hargata/146.changes
Display current odometer when incrementing.
2025-03-22 18:49:01 -06:00
Hargata Softworks
83fc8b8682
Merge pull request #891 from hargata/Hargata/889
remove unused aggregation.
2025-03-22 18:20:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
89fa15bbb7 remove unused aggregation. 2025-03-22 18:20:29 -06:00
Hargata Softworks
5e69be56aa
Merge pull request #890 from hargata/Hargata/889
Fixed Average MPG label changing when consumption units are changed.
2025-03-22 18:11:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b1112dc617 Fixed Average MPG label changing when consumption units are changed. 2025-03-22 18:04:41 -06:00
Hargata Softworks
5a31460afe
Merge pull request #886 from hargata/Hargata/server.config
Review server configs
2025-03-20 11:26:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2bcedbc7d4 translation keys 2025-03-20 11:25:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5047fdf1bc fixed button styling. 2025-03-20 11:23:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7c8c3fb1c8 added test email 2025-03-20 10:34:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
9920bd472f added oidc config 2025-03-20 09:49:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d7839a8a05 Allow users to see server configuration passed in via environment variables or appsettings.json 2025-03-19 12:57:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7e07e73ef5 Display current odometer when incrementing. 2025-03-18 12:41:02 -06:00
Hargata Softworks
72a5960d40
Merge pull request #873 from hargata/Hargata/update.configurator
Fix Favicon Path
2025-02-27 12:04:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3429f1e4f9 Fix Favicon Path 2025-02-27 12:03:22 -07:00
Hargata Softworks
f8bea8bf81
Merge pull request #872 from hargata/Hargata/update.configurator
Update Configurator
2025-02-27 11:58:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
43794dd223 add feature to automatically merge new configurations into existing appsettings.json 2025-02-27 11:57:18 -07:00
Hargata Softworks
5cc84a7b46
Merge pull request #866 from hargata/Hargata/enter.key.qol
use generic enter key callback to handle key presses.
2025-02-19 16:09:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48248a4386 use generic enter key callback to handle key presses. 2025-02-19 16:08:15 -07:00
Hargata Softworks
95305402e6
Merge pull request #864 from hargata/Hargata/856
add markdown to kiosk notes.
2025-02-19 09:12:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
29f24c527f add markdown to kiosk notes. 2025-02-19 09:10:03 -07:00
Hargata Softworks
efa2bbf6cc
Merge pull request #858 from hargata/Hargata/857
Add flag to print individual records
2025-02-12 10:24:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
66ed9ba699 add flag to print individual records when generating vehicle history report. 2025-02-12 10:23:19 -07:00
Hargata Softworks
e841e64f78
Merge pull request #852 from hargata/Hargata/dynamic.csv
dynamically generate locale-sensitive CSV samples
2025-02-10 11:15:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b0f46803c5 dynamically generate locale-sensitive CSV samples 2025-02-10 11:13:53 -07:00
Hargata Softworks
ac0bef5de0
Merge pull request #850 from hargata/Hargata/840.2
more hardening.
2025-02-07 12:37:22 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08ea40a08e more hardening. 2025-02-07 12:36:43 -07:00
Hargata Softworks
1d8e8059cd
Merge pull request #849 from hargata/Hargata/848
fixed MPG label bugs when all tagged records are partial fuel ups.
2025-02-07 08:33:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
20b4396a4c simplified code. 2025-02-07 08:32:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4c39cb4d06 fixed MPG label bugs when all tagged records are partial fuel ups. 2025-02-07 08:29:18 -07:00
Hargata Softworks
f58e6abd9f
Update issue templates 2025-02-06 22:48:32 -07:00
Hargata Softworks
4f1e83a7d7
Update issue templates 2025-02-06 22:46:55 -07:00
Hargata Softworks
e39b38c2d5
Update CONTRIBUTING.md 2025-02-06 22:35:38 -07:00
Hargata Softworks
b9ffa6ce91
Merge pull request #845 from hargata/Hargata/780
further hardening.
2025-02-05 16:28:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
76c6753785 further hardening. 2025-02-05 16:27:45 -07:00
Hargata Softworks
54f5062377
Merge pull request #844 from hargata/Hargata/780
add functionality to re-arrange table columns.
2025-02-05 16:01:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7e60fd9e40 reset var. 2025-02-05 16:00:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
926947bae4 add functionality to re-arrange table columns. 2025-02-05 15:56:20 -07:00
Hargata Softworks
2652e47018
Merge pull request #841 from hargata/Hargata/840
Hargata/840
2025-02-05 07:06:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1500b6ed0 bump version 2025-02-04 13:22:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
125bc44d2e add put and delete api endpoints. 2025-02-04 13:15:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c317c0a058 Add POST API Endpoint to add plan records. 2025-02-04 10:07:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
beb9498399 standardize this stuff. 2025-02-03 16:50:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4e74940684 added whoami endpoint 2025-02-03 16:02:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c0f73080d2 Add APIAuth Role 2025-02-03 13:34:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cd0a35537b API endpoint for Planner 2025-02-03 13:20:08 -07:00
Hargata Softworks
4d5020b607
Merge pull request #839 from hargata/Hargata/805
re-send registration token email.
2025-02-03 08:51:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7d8b7596ce fixed wording. 2025-02-03 08:51:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08b5c9f25a re-send registration token email. 2025-02-03 08:35:33 -07:00
Hargata Softworks
82a4f8d57b
Merge pull request #833 from hargata/Hargata/831
translatable Email correspondence
2025-01-26 10:28:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
538a79319e translatable Email correspondence 2025-01-26 10:26:41 -07:00
Hargata Softworks
0555dcbc43
Merge pull request #830 from hargata/Hargata/824
take into account purchase and sold date.
2025-01-24 14:02:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b1f19ff9f take into account purchase and sold date. 2025-01-24 14:00:26 -07:00
Hargata Softworks
9cac427eb9
Merge pull request #829 from hargata/Hargata/823
add attachments column
2025-01-24 13:13:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1e5950028f add attachments column 2025-01-24 13:01:56 -07:00
Hargata Softworks
7a7d343c3f
Merge pull request #828 from hargata/Hargata/824
additional logic taking into account vehicle sold date.
2025-01-24 10:40:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ebf6388414 additional logic taking into account vehicle sold date. 2025-01-24 10:39:42 -07:00
Hargata Softworks
fa5426be53
Merge pull request #827 from hargata/Hargata/824
Fix number of days used when calculating cost per day.
2025-01-24 10:22:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4c60bb20c9 Fix number of days used when calculating cost per day. 2025-01-24 10:21:40 -07:00
Hargata Softworks
b02f3c8f8b
Merge pull request #826 from hargata/Hargata/825
825
2025-01-24 09:17:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d5f769e5a4 Fixed the cost per distance sum table to use total cost per year divided by total distance per year. 2025-01-24 09:16:42 -07:00
Hargata Softworks
2afd1188eb
Merge pull request #820 from hargata/Hargata/804
only make certain api endpoints testable
2025-01-22 12:09:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c6301242d make urgencies endpoint parameters optional. 2025-01-22 12:07:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
06016727d9 only make certain api endpoints testable 2025-01-22 11:52:16 -07:00
Hargata Softworks
1ee0c27ff9
Merge pull request #819 from hargata/Hargata/804
make it easier to test API.
2025-01-22 11:34:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7f03a630c6 make it easier to test API. 2025-01-22 11:33:55 -07:00
Hargata Softworks
2aa19f4f3b
Merge pull request #818 from hargata/Hargata/tag.bug.fix
Fix count labels for odometer and reminder records when filtered by tags
2025-01-22 09:40:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4be3c16adc Fix count labels for odometer and reminder records when filtered by tags. 2025-01-22 09:39:07 -07:00
Hargata Softworks
f1f99a67dd
Merge pull request #816 from hargata/Hargata/refine.sort
only add the default sorter once.
2025-01-21 15:59:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a4f15ffe15 only add the default sorter once. 2025-01-21 15:57:50 -07:00
Hargata Softworks
c11dad0cb3
Merge pull request #815 from hargata/Hargata/refine.sort
get rid of storedRowTableState and all the bugs that comes with it.
2025-01-21 14:11:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2bf2469657 get rid of storedRowTableState and all the bugs that comes with it. 2025-01-21 14:08:44 -07:00
Hargata Softworks
a23b02e962
Merge pull request #814 from hargata/Hargata/805.2
streamline token generation process.
2025-01-20 13:14:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
905ee9bf27 streamline token generation process. 2025-01-20 13:14:04 -07:00
Hargata Softworks
e26869b30b
Merge pull request #813 from hargata/Hargata/755
add days as an interval for recurring tax records and reminders
2025-01-20 12:55:56 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90867792d9 missing semicolon 2025-01-20 12:51:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1238802a75 More Month to Time update. 2025-01-20 12:51:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f7c50b88f Change label. 2025-01-20 12:48:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
df24434689 add days as an interval for recurring tax records and reminders 2025-01-20 12:42:59 -07:00
Hargata Softworks
878748803d
Merge pull request #812 from hargata/Hargata/tech.debt
Fixed unreachable code and used Append instead of Add.
2025-01-19 12:34:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
80bbf7f22f Fixed unreachable code and used Append instead of Add. 2025-01-19 12:33:50 -07:00
Hargata Softworks
28b0ea565a
Merge pull request #811 from hargata/Hargata/801.3
Add sticker printing for supplies.
2025-01-19 10:37:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
07ccd1dea9 Add sticker printing for supplies. 2025-01-19 10:36:32 -07:00
Hargata Softworks
6a8fba535a
Merge pull request #810 from hargata/Hargata/801.2
added extra field and supplies to the sticker print.
2025-01-17 11:37:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f1b82ad0d3 removed unused row class. 2025-01-17 11:36:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0035bc09dd added extra field and supplies to the sticker print. 2025-01-17 11:35:41 -07:00
Hargata Softworks
863c57d7fb
Merge pull request #808 from hargata/Hargata/801.2
add sticker printing for odometer and plan records.
2025-01-17 09:11:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
00ccb68ce2 add sticker printing for odometer and plan records. 2025-01-17 09:09:19 -07:00
Hargata Softworks
c2995dcd25
Merge pull request #806 from hargata/Hargata/805
enable open registration.
2025-01-16 11:23:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b63366627 enable open registration. 2025-01-16 11:20:44 -07:00
Hargata Softworks
ababa6bf27
Merge pull request #803 from hargata/Hargata/801
Hargata/801 - Printable Stickers
2025-01-16 10:24:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f1f42d4f0 add vehicle extra fields and fixed styling 2025-01-16 10:24:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d76bd6d36 added printing functionality for notes, fuel, and tax. 2025-01-15 11:54:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ab34d1682c Added ability to print individual service, repair, and upgrade records. 2025-01-15 09:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
552b7a6356 simplified design. 2025-01-12 15:26:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f86acf673a you can now print reminders. 2025-01-12 14:47:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c6f49bafca Added sticker viewModel and some code cleanup. 2025-01-12 12:28:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fdbb325611 simplify code. 2025-01-12 12:04:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8c557ced85 draft PR for sticker printing. 2025-01-11 12:04:01 -07:00
Hargata Softworks
ad0f7de506
Merge pull request #802 from hargata/Hargata/697
Document the Calendar API along with support for auth.
2025-01-10 19:46:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
15ec9bb454 Document the Calendar API along with support for auth. 2025-01-10 19:43:09 -07:00
Hargata Softworks
9fe7558cfe
Merge pull request #792 from hargata/Hargata/260
Simplify volume mounts.
2025-01-10 13:53:31 -07:00
Hargata Softworks
79633dbe1d
Merge pull request #799 from hargata/Hargata/796
Update configurator
2025-01-10 12:13:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f325306e20 Update configurator 2025-01-10 12:13:34 -07:00
Hargata Softworks
4778656063
Merge pull request #798 from hargata/Hargata/796
fix mileage not included.
2025-01-10 11:02:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f7e00523c2 fix mileage not included. 2025-01-10 11:01:24 -07:00
Hargata Softworks
bf3df7230b
Merge pull request #797 from hargata/Hargata/796
Hargata/796
2025-01-10 10:03:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c5182e0ed6 lol 2025-01-10 10:03:09 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4081438cba Defer calculations if odometer not provided. 2025-01-10 10:01:16 -07:00
Hargata Softworks
b72fe2bf37
Merge pull request #793 from hargata/Hargata/warn.dir.diff
Warn when working directory differs from content path.
2025-01-09 10:02:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d9c687709 Warn when working directory differs from content path. 2025-01-09 10:02:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3d4b970967 Simplify volume mounts. 2025-01-08 20:56:37 -07:00
Hargata Softworks
792f295c45
Merge pull request #791 from hargata/Hargata/769
Fixed backup restoration for userConfig.json
2025-01-08 20:38:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
af28753558 use a static string instead. 2025-01-08 20:37:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
24fb663599 Fixed backup restoration for userConfig.json 2025-01-08 20:33:38 -07:00
Hargata Softworks
140506c9c3
Merge pull request #790 from hargata/Hargata/769
moved userConfig file into data folder.
2025-01-08 20:26:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
519f159c8c moved userConfig file into data folder. 2025-01-08 20:25:17 -07:00
Hargata Softworks
bb019cbcd9
Merge pull request #789 from hargata/Hargata/769
Fix gas record files attachment
2025-01-08 16:19:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
84219627ff Fix gas record files attachment 2025-01-08 16:18:36 -07:00
Hargata Softworks
f218e878c6
Merge pull request #788 from hargata/Hargata/769
Document Upload Endpoint
2025-01-08 15:55:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
520f47955b cleaned up usings. 2025-01-08 14:15:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8110ee18f1 Create endpoint to upload documents and add ability to attach uploaded documents to records. 2025-01-08 14:14:23 -07:00
Hargata Softworks
55bf817310
Merge pull request #782 from hargata/Hargata/697
added API method that generates a calendar of reminders for the user.
2025-01-08 12:39:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2f55f8118 Restore favicon, previously was served up automatically by browser but now we want to link it explicitly. also brought back the security features for accessing static files. 2025-01-08 12:38:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f48e7cd0d4 update Controller Action Name, 2025-01-08 11:11:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c82e0c8b9b Use a MD5 hash to get exactly 16 bytes so the GUID is always valid and identical for the calendar event with same date and description. 2025-01-08 10:49:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c72877e16b Merge branch 'main' into Hargata/697 2025-01-08 10:40:10 -07:00
Hargata Softworks
ccc9076397
Merge pull request #786 from hargata/Hargata/785
move files out of webrootpath.
2025-01-08 10:13:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c716ecf4a bump version 2025-01-08 10:12:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2cc471b944 move files out of webrootpath. 2025-01-08 10:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a6c450109a added API method that generates a calendar of reminders for the user. 2025-01-07 08:43:25 -07:00
Hargata Softworks
e13707305b
Merge pull request #776 from hargata/Hargata/745
Fix annoying bug on macOS devices where context menu hides as soon as…
2024-12-31 09:21:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
68bc0383f4 Fix annoying bug on macOS devices where context menu hides as soon as it shows up. 2024-12-31 09:21:05 -07:00
Hargata Softworks
878f4f3687
Merge pull request #771 from hargata/Hargata/745
added bottom padding for garage view.
2024-12-16 17:32:20 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48a721adda added bottom padding for garage view. 2024-12-16 17:31:30 -07:00
Hargata Softworks
548e8da78c
Merge pull request #747 from hargata/Hargata/745
1.4.2 Changes
2024-12-16 09:45:01 -07:00
Hargata Softworks
beb28214cf
Merge pull request #767 from hargata/Hargata/758
allow users to create odometer records from existing records.
2024-12-13 11:37:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b4bb31491b allow users to create odometer records from existing records. 2024-12-13 11:31:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0b5f007327 Styling fixes. 2024-12-13 10:59:53 -07:00
Hargata Softworks
d31a4aeea3
Merge pull request #766 from hargata/Hargata/761
Add Date Range Filter to Vehicle Maintenance Report
2024-12-12 11:58:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
97acd873eb Added date range filter. 2024-12-12 11:56:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5cf3538be3 garage view enhancement for mobile devices. 2024-12-11 19:19:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a34921701d webhook payload update. 2024-12-11 14:40:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5fcf54de09 cleaned up some code. 2024-12-11 12:32:40 -07:00
Hargata Softworks
68372bf995
Merge pull request #764 from hargata/Hargata/763
Hargata/763
2024-12-11 12:29:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9f99ac1531 Add tags to be exported. 2024-12-11 12:28:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7384fda6b Spacing and translation keys. 2024-12-07 22:34:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f4d610825 add spacing in report parameter. 2024-12-07 22:25:28 -07:00
Hargata Softworks
6bbf4b2575
Merge pull request #760 from hargata/Hargata/572
Hargata/572
2024-12-07 12:03:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
32db884620 Allow users to filter records by tags in the consolidated report. 2024-12-07 11:50:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1b736b36f8 Added delete records. 2024-12-06 11:34:04 -07:00
Hargata Softworks
22dfe5eb04
Merge pull request #756 from hargata/Hargata/574
Created nicer machine payloads for webhooks.
2024-12-06 08:43:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8fd49e20d5 added support for discord. 2024-12-06 08:42:25 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c791070126 Fixed webhook stuff. 2024-12-06 08:11:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
807603fd4f Created nicer machine payloads for webhooks. 2024-12-05 19:08:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a944e5e5ab See #744 2024-12-05 08:10:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
33e916cac2 force a page refresh if there are oudated recurring tax records on vehicle load. 2024-12-04 21:11:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a99d71dc41 pattern 2024-12-04 11:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
67d91f1a0d See 751 2024-12-04 11:38:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
de2db7d24e added security checks 2024-12-04 09:05:49 -07:00
Hargata Softworks
b360f77ea0
Merge pull request #754 from hargata/Hargata/752.2
Fixed naming policy.
2024-12-04 08:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e869528522 Fixed naming policy. 2024-12-04 08:15:46 -07:00
Hargata Softworks
7c0317d232
Merge pull request #753 from hargata/Hargata/752
Allow for JSON Body in POST/PUT API Methods
2024-12-03 14:15:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0e49497da1 added invariant exports to get methods. 2024-12-03 13:40:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0380382c47 allow API methods to receive JSON with rich datatypes. 2024-12-03 11:35:11 -07:00
Hargata Softworks
23201b8c86
Merge pull request #748 from hargata/Hargata/541
Added PUT API Methods
2024-11-30 12:25:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5f357c9f84 Fix CollaboratorFilter bug. 2024-11-30 12:23:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
244272621c bump version 2024-11-30 10:19:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5792b84880 Added PUT API Methods 2024-11-30 10:17:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8e644093f9 added try catch 2024-11-29 13:47:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9421cb57ca Create past records for outdated tax records. 2024-11-29 13:37:37 -07:00
Hargata Softworks
bd7cbffe10
Merge pull request #741 from hargata/Hargata/60
SimplyAuto Import
2024-11-25 09:27:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b293882f75 add more column aliases. 2024-11-25 09:26:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0984b2049b Added support for SimplyAuto Fuel Import. 2024-11-25 09:18:10 -07:00
Hargata Softworks
d4ec4989e7
Merge pull request #740 from hargata/Hargata/739
Refactored code in ConfigHelper.
2024-11-25 08:42:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
230433f784 Refactored a few more methods that rely on the IConfiguration object to use IConfigHelper instead. 2024-11-25 08:40:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d4dda481be pattern. 2024-11-25 08:18:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1d3b94f544 Refactored code in ConfigHelper. 2024-11-25 08:14:56 -07:00
Hargata Softworks
78434f0016
Merge pull request #737 from hargata/Hargata/overhaul.reminder.tab
Reminder Record Tab overhaul.
2024-11-24 20:42:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c75145de49 Reminder Record Tab overhaul. 2024-11-24 20:19:48 -07:00
Hargata Softworks
5e3480f9f3
Merge pull request #736 from hargata/Hargata/context.menu.remaster
Remaster the context menu
2024-11-24 14:41:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f74fdc8ed4 context menu remaster for both notes and reminders. 2024-11-24 14:40:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ba62f8bf8 prevent multiple gas record edits if selected ids is less than 2 2024-11-24 13:48:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2b9dea2b6 Added context menu to plan records. 2024-11-24 13:47:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f92aa22375 Remaster the context menu 2024-11-23 22:22:16 -07:00
Hargata Softworks
d65001f412
Merge pull request #735 from hargata/Hargata/costtable.row.highlight
added row highlighting when viewing cost tables.
2024-11-23 15:34:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ea223cd0a added row highlighting when viewing cost tables. 2024-11-23 15:34:07 -07:00
Hargata Softworks
addf73bc27
Merge pull request #734 from hargata/Hargata/fix.aggregate.label
Fuel Consumption label now updates based on fuel consumption unit sel…
2024-11-23 11:32:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f853d0d5e Fuel Consumption label now updates based on fuel consumption unit selected. 2024-11-23 11:31:48 -07:00
Hargata Softworks
4c46278be1
Merge pull request #733 from hargata/Hargata/select.mode
Added select mode.
2024-11-22 14:06:53 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0edc51083c Added select mode. 2024-11-22 14:04:36 -07:00
Hargata Softworks
d906bec333
Merge pull request #732 from hargata/Hargata/fix.demo
more minor changes.
2024-11-22 10:57:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a47c4942b7 more minor changes. 2024-11-22 10:56:53 -07:00
Hargata Softworks
cd8df9e938
Merge pull request #731 from hargata/Hargata/fix.demo
Update the demo db.
2024-11-22 10:53:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5c37f98274 Update the demo db. 2024-11-22 10:51:08 -07:00
Hargata Softworks
f452436dbd
Merge pull request #730 from hargata/Hargata/523
Sort notes in ascending order by description
2024-11-22 10:27:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9375bb103b spacing. 2024-11-22 10:25:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3222579cb4 allow notes to be sorted in ascending order as well as minor code clean up. 2024-11-22 10:20:53 -07:00
Hargata Softworks
4348b50e54
Merge pull request #728 from hargata/Hargata/716
additional enhancements
2024-11-21 23:15:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b7c58b6e8 additional enhancements 2024-11-21 23:15:05 -07:00
Hargata Softworks
664b89a5df
Merge pull request #727 from hargata/Hargata/716
Added functionality to display multi year trends.
2024-11-21 22:14:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
628c22ad61 Added functionality to display multi year trends. 2024-11-21 22:13:08 -07:00
Hargata Softworks
db62a041c4
Merge pull request #726 from hargata/Hargata/454.2
Fixed wording.
2024-11-21 15:07:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ea3ffea797 Fixed wording. 2024-11-21 15:07:24 -07:00
Hargata Softworks
c49c8d67a2
Merge pull request #725 from hargata/Hargata/454
Allow users to add supplies onto existing records.
2024-11-21 13:56:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b01df4c56 Updated verbiage 2024-11-21 13:51:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9221516bad fix bug when there are multiple supplies with the same id. 2024-11-20 21:18:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1988cf54f5 Allow users to add supplies onto existing records. 2024-11-20 20:48:38 -07:00
Hargata Softworks
8a8d8979a7
Merge pull request #724 from hargata/Hargata/429
Replenish Supplies By Deleting Records
2024-11-20 12:14:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e983b39f8e Fixed minor bug with plan type not displaying correctly in kiosk mode. 2024-11-20 12:13:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6c5cd4b46 important bug fix. 2024-11-20 11:57:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
210b27ab25 Allow users to to restore supplies by either deleting the record or deleting the requistion history. 2024-11-20 11:54:29 -07:00
Hargata Softworks
63e9e5ecec
Merge pull request #720 from hargata/Hargata/planner.drag.drop.fix
1.4.1 Changes
2024-11-19 14:45:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
771ca15ea2 bump version. 2024-11-19 13:42:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6b403b5e2 styling fixes. 2024-11-19 13:41:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9976c94481 Merge branch 'main' into Hargata/planner.drag.drop.fix 2024-11-19 13:31:29 -07:00
Hargata Softworks
b9dca8ceae
Merge pull request #722 from hargata/Hargata/operationresponse.cleanup.2
Added Conditional OperationalResponse constructor.
2024-11-19 13:14:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
03b9331a9a Added Conditional OperationalResponse constructor. 2024-11-19 13:04:40 -07:00
Hargata Softworks
f629834d6a
Merge pull request #721 from hargata/Hargata/operationresponse.cleanup.1
Clean up OperationResponse method Part 1
2024-11-19 12:37:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
25952cce50 Clean up OperationResponse method Part 1 2024-11-19 12:26:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6eba83ccec added enhancement to bulk gas edit modal 2024-11-19 10:46:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3c1ce80528 Added user setting for fixing gas consumption to two decimal places. 2024-11-19 10:43:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7851e87b2 Save Vehicle Specific Parameters in SessionStorage. 2024-11-19 09:11:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d8e06fd968 Fix an issue with the drag and drop functionality on the planner page. 2024-11-17 11:02:11 -07:00
Hargata Softworks
c5a5de50a7
Merge pull request #712 from hargata/Hargata/702
simplify code.
2024-11-12 16:02:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7715e7b4ab simplify code. 2024-11-12 16:02:18 -07:00
Hargata Softworks
5a3ed3d3d4
Merge pull request #711 from hargata/Hargata/702
set width
2024-11-12 14:14:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1e2394194 set width 2024-11-12 13:01:10 -07:00
Hargata Softworks
6abb569df1
Merge pull request #710 from hargata/Hargata/702
User customizable fields in Vehicle Maintenance Report
2024-11-12 12:59:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e59e33bc3b fix bug. 2024-11-12 12:58:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a671dc1937 allow users to define what field sthey want to see in vehicle maintenance report. 2024-11-12 12:54:39 -07:00
Hargata Softworks
426b52aee4
Merge pull request #709 from hargata/Hargata/702
show custom logos in reports and add a banner for credit instead.
2024-11-11 20:36:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
87fe011565 show custom logos in reports and add a banner for credit instead. 2024-11-11 20:35:09 -07:00
Hargata Softworks
30c03af5b0
Merge pull request #707 from hargata/Hargata/706
Further translation editor enhancements
2024-11-11 11:54:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c9c8fc8705 ensures that all future translations check if the default translation has the key. 2024-11-11 09:06:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9cd8030808 see requirements for 706 2024-11-11 09:03:22 -07:00
Hargata Softworks
5411d4152c
Merge pull request #704 from Scorpoon/Code-CleanUp-1
#1 Code clean up
2024-11-11 08:08:50 -07:00
Hargata Softworks
c5a05afe1c
Merge pull request #703 from Scorpoon/Missing-Translations
Added missing translations
2024-11-10 07:34:50 -07:00
Scorpoon
5b44f2ae33 Renamend UserAccessDataAcces.cs to UserAccessDataAccess.cs 2024-11-10 15:09:19 +01:00
Scorpoon
039b30da4f Optimized imports & removed redundant qualifiers 2024-11-10 14:57:50 +01:00
Scorpoon
df45378bce Added missing translation logic for "Upload documents(optional)" in _UpgradeRecordModal.cshtml 2024-11-10 14:10:49 +01:00
Scorpoon
3729d2c23f - Added translation for "No Recurring Reminders Found" in _RecurringReminderSelector.cshtml
- Added translation for "No Vehicles Available" in _VehicleSelector.cshtml
- Added translations for the DB-Migration tool
- Added the new translation records to the default language file en_US.json
2024-11-10 13:30:07 +01:00
Hargata Softworks
89ece413c8
Merge pull request #696 from hargata/Hargata/custom.widgets
require environment variable to be injected for security reasons.
2024-11-04 11:58:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1b361397f require environment variable to be injected for security reasons. 2024-11-04 11:57:27 -07:00
Hargata Softworks
ada0715343
Merge pull request #695 from hargata/Hargata/custom.widgets
Custom Widgets
2024-11-04 09:52:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5cdc687f6d backup widgets and further enhance the cleanup api method. 2024-11-04 09:51:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48f0e16fde additional enhancements to widget editor. 2024-11-04 08:37:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d6b6600ce9 Custom Widgets 2024-11-03 13:22:48 -07:00
Hargata Softworks
82634e9e1b
Merge pull request #694 from hargata/Hargata/kiosk.token
more kiosk fixes.
2024-11-03 09:17:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
225e5e58bc more kiosk fixes. 2024-11-03 09:16:46 -07:00
Hargata Softworks
21d135a8ab
Merge pull request #693 from hargata/Hargata/order.missing.supplies
Add functionality to order insufficient supplies for plan templates.
2024-11-02 20:41:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7a0636a48a add cancel button to modal. 2024-11-02 20:40:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0b9ca77281 Add functionality to order insufficient supplies for plan templates. 2024-11-02 19:13:02 -06:00
Hargata Softworks
feaf631b47
Merge pull request #692 from hargata/Hargata/611
Styling fixes for supply selector
2024-11-02 16:45:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec24c19821 Styling fixes for supply selector 2024-11-02 16:44:27 -06:00
Hargata Softworks
36a120fa0f
Merge pull request #691 from hargata/Hargata/611
automatic decimal formatting.
2024-11-02 15:47:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fb169d5054 automatic decimal formatting. 2024-11-02 15:46:21 -06:00
Hargata Softworks
59fa7b6a1d
Merge pull request #688 from hargata/Hargata/526
Add functionality to duplicate records to other vehicles.
2024-11-01 21:08:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
201d832b92 Add functionality to duplicate records to other vehicles. 2024-11-01 21:05:46 -06:00
Hargata Softworks
31b4ef5458
Merge pull request #685 from hargata/Hargata/bump.version
bump version.
2024-11-01 16:59:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b06ad0cdaa bump version and kiosk enhancement. 2024-11-01 16:59:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3914bc3b7 bump version. 2024-10-31 23:54:09 -06:00
Hargata Softworks
aea870974b
Merge pull request #683 from hargata/Hargata/kiosk
Added kiosk view.
2024-10-31 23:51:21 -06:00
Hargata Softworks
7f6265ba05
Merge pull request #681 from hargata/Hargata/fix.translation.editor
Fixed minor bug with translation editor.
2024-10-31 23:51:00 -06:00
DESKTOP-T0O5CDB\DESK-555BD
85542cb278 minor feature enhancement for #684 2024-10-31 23:49:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f6e0873d83 fix small screen spacing. 2024-10-31 23:41:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
745b262dfc Added kiosk view. 2024-10-31 22:39:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
73df755f70 remove unncessary slash 2024-10-31 18:19:08 -06:00
DESKTOP-T0O5CDB\DESK-555BD
762a1b98d3 Fixed minor bug. 2024-10-31 14:49:51 -06:00
Hargata Softworks
571e87dd14
Merge pull request #673 from hargata/Hargata/update.docs
Update screenshots and docs site.
2024-10-31 13:33:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
22eb38709a added show password button, updated configurator, fixed login page width. 2024-10-31 09:41:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
986a8420f8 Merge branch 'main' into Hargata/update.docs 2024-10-30 10:43:33 -06:00
Hargata Softworks
cff5f8e8e9
Merge pull request #679 from hargata/Hargata/518.2
Add ability to re-order tabs.
2024-10-30 09:36:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0df6cea88c backend for saving tab order 2024-10-30 09:33:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
88ff2432f6 Frontend for reordering tabs. 2024-10-29 23:18:31 -06:00
Hargata Softworks
efb7fb999b
Merge pull request #677 from hargata/hargata-patch-1
Update screenshots.md
2024-10-28 20:48:35 -06:00
Hargata Softworks
1a2b503791
Update screenshots.md 2024-10-28 20:47:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e68d4ffa3a Merge branch 'main' into Hargata/update.docs 2024-10-28 19:21:50 -06:00
Hargata Softworks
d701a8964f
Merge pull request #676 from hargata/Hargata/ios.resizebug
fix bug with iOS closing mobile nav when scrolling
2024-10-28 19:19:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b6929113bc fix bug with iOS closing mobile nav when scrolling 2024-10-28 19:18:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fc1eac1049 Merge branch 'main' into Hargata/update.docs 2024-10-28 15:58:56 -06:00
Hargata Softworks
189659ff1e
Merge pull request #674 from hargata/Hargata/fix.wonky.columns
Fix bug where extra fields causes columns to be misaligned.
2024-10-28 15:58:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cb19130119 Fix bug where extra fields causes columns to be misaligned. 2024-10-28 15:52:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1596bf1deb Update screenshots and docs site. 2024-10-28 10:11:34 -06:00
Hargata Softworks
636cbb3d21
Merge pull request #672 from hargata/Hargata/translation.getter
further hardening.
2024-10-28 09:55:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cff7845dd4 further hardening. 2024-10-28 09:19:09 -06:00
Hargata Softworks
2d9cc3507f
Merge pull request #671 from hargata/Hargata/translation.getter
Add functionality to download all translations.
2024-10-27 19:04:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4c6ac96dd1 Add functionality to download all translations. 2024-10-27 19:01:53 -06:00
Hargata Softworks
e5a594367e
Merge pull request #670 from hargata/Hargata/translation.getter
Add translation getter
2024-10-27 09:54:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
12aaf8c209 spacing issue 2024-10-27 09:53:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cafbb156af Add translation getter 2024-10-27 09:52:21 -06:00
Hargata Softworks
36ac61d848
Merge pull request #669 from hargata/Hargata/translation.editor
fixed height for built in translator.
2024-10-25 16:56:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
df066a593e fixed height for built in translator. 2024-10-25 16:56:24 -06:00
Hargata Softworks
7ac9185e4c
Merge pull request #668 from hargata/Hargata/translation.editor
Added translation editor.
2024-10-25 11:08:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
090cbba1a0 Added translation editor. 2024-10-25 11:06:40 -06:00
Hargata Softworks
5e3529f9cc
Merge pull request #667 from hargata/Hargata/gps.timeout.fix
Fix distance accuracy
2024-10-24 22:35:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f90ddeabb4 Fix distance accuracy 2024-10-24 22:34:53 -06:00
Hargata Softworks
f54419b993
Merge pull request #666 from hargata/Hargata/gps.timeout.fix
set time out to 4 seconds and max age to 1 second to improve accuracy
2024-10-24 14:14:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cbbd7ff25f set time out to 4 seconds and max age to 1 second to improve accuracy 2024-10-24 14:13:47 -06:00
Hargata Softworks
0c2c089828
Merge pull request #664 from hargata/Hargata/485
hide identifier selector if no extra fields configured.
2024-10-23 11:08:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6a1813399f hide identifier selector if no extra fields configured. 2024-10-23 11:07:23 -06:00
Hargata Softworks
4c8d2b3704
Merge pull request #663 from hargata/Hargata/485
Allow users to select any extra field as vehicle identifier.
2024-10-23 10:54:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
23f97ebd46 Allow users to select any extra field as vehicle identifier. 2024-10-23 10:52:41 -06:00
Hargata Softworks
e3dccf0182
Merge pull request #662 from hargata/Hargata/gps.integration
further hardening
2024-10-22 11:32:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
272596878c further hardening 2024-10-22 11:25:48 -06:00
Hargata Softworks
46953e9063
Merge pull request #659 from hargata/Hargata/gps.integration
further bug fixes and hardening.
2024-10-21 10:53:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
30f35855f2 further bug fixes and hardening. 2024-10-21 10:53:09 -06:00
Hargata Softworks
df94132917
Merge pull request #658 from hargata/Hargata/gps.integration
Hargata/gps.integration
2024-10-20 20:59:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
de37d689be moved variable to partial view. 2024-10-20 20:51:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4f941aad52 add functionality to show sub odometer readings. 2024-10-20 20:41:43 -06:00
Hargata Softworks
2febdf1259
Merge pull request #657 from hargata/Hargata/gps.integration
fixes for GPS integration and experimental feature banner.
2024-10-20 19:12:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
349808a609 fixes for GPS integration and experimental feature banner. 2024-10-20 19:10:23 -06:00
Hargata Softworks
7d58d1bc40
Merge pull request #656 from hargata/Hargata/gps.integration
GPS methods to track vehicle odometer. experimental feature.
2024-10-20 15:31:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5e91a26230 GPS methods to track vehicle odometer. experimental feature. 2024-10-20 15:30:22 -06:00
Hargata Softworks
0942ae1742
Merge pull request #655 from hargata/Hargata/chore.update.deps
CHORE: Update Dependencies
2024-10-18 09:42:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cce9bd29e5 z 2024-10-18 09:42:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
05fdeef862 CHORE: Update Dependencies 2024-10-18 09:41:44 -06:00
Hargata Softworks
dfa08b287c
Merge pull request #654 from hargata/Hargata/fix.tabs
improve tabs appearance
2024-10-17 15:06:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
77920f94cd improve tabs appearance 2024-10-17 15:05:20 -06:00
Hargata Softworks
3631a036c6
Merge pull request #653 from hargata/Hargata/616
thumbnail resize using Hermite interpolation
2024-10-17 10:37:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1445959d05 thumbnail resize using Hermite interpolation 2024-10-17 10:34:00 -06:00
Hargata Softworks
0b45de5ba8
Merge pull request #652 from hargata/Hargata/fix.titles
Fixed the Titles.
2024-10-16 15:25:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d86cdfcefd Fixed the Titles. 2024-10-16 14:25:19 -06:00
Hargata Softworks
0c7aa304f7
Create CONTRIBUTING.md 2024-10-16 12:36:38 -06:00
Hargata Softworks
e5114f491d
Merge pull request #651 from hargata/Hargata/fix.adjustedodomete
1.3.9 Changes
2024-10-15 12:43:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e238bd8a12 translation keys 2024-10-15 11:50:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f53ad74c72 fix bugs with alternate fuel units when the average min and max labels don't exist. 2024-10-14 21:37:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3d456d1cd5 quick styling fix 2024-10-14 10:16:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
de32fe4d85 load sponsors in ajax to account for users with slower internet connection. 2024-10-14 09:42:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
29a90fe814 add note records to attachment export. 2024-10-13 12:25:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3f31f091dc fixed bug with UK units. 2024-10-10 15:41:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
571b558181 allow extra fields edit for multiple odometer records. 2024-10-10 09:38:04 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ed0775ab71 Display fuel mileage unit and fix bug with metric fuel mileages 2024-10-10 10:08:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
19e19d7c15 extra field edit for multiple gas records. 2024-10-09 22:23:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b7e574889c Allow Extra Fields to be edited for multiple records for Service, Repair, and Upgrade Records. 2024-10-09 21:29:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2a66c97254 separated out the import and export methods. 2024-10-09 16:02:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e14ef961b9 improved maintainability by splitting massive controller down to sections. 2024-10-09 15:58:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec2c17be23 updated configurator and streamlined getAndValidateExtraFields method. 2024-10-09 09:13:07 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b5162d2044 configurator webpage 2024-10-05 20:04:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ae428b94d1 Update ENV file to direct users to configurator 2024-10-05 20:04:07 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3350f3c39a confirm deletion. 2024-10-05 18:40:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
68caf82167 moved token into modal. 2024-10-05 18:02:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8c5f1c3a01 fixed minor bug issue in admin panel page and allow for multiple email addresses to generate token for 2024-10-05 17:28:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e1bbc232e1 removed icon. 2024-10-05 16:12:48 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fbc7a39996 Merge branch 'Hargata/admin.panel.enhancement' into Hargata/fix.adjustedodomete 2024-10-05 16:03:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1ce77ad1ee create app schema if not exists without relying on init.sql 2024-10-05 12:03:46 -06:00
DESKTOP-T0O5CDB\DESK-555BD
08cee8029f more ctrl enter forms. 2024-09-29 19:44:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e33bcf5e57 re-align some settings switches 2024-09-29 15:25:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cf5ceaa959 Ctrl and Enter to submit record forms 2024-09-29 15:13:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8293bed89c added warning for when user is running LubeLogger without a preconfigured culture. 2024-09-29 10:58:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a830580c27 moved delete vehicle button to edit vehicle modal. 2024-09-29 10:31:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
94c6cca25c add enter event on sweetalert modals see #639 2024-09-28 12:44:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78e0a0146c chore: fix deprecated click function on DOM elements 2024-09-28 12:05:48 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ea667ed950 visual enhancements for admin panel 2024-09-28 10:44:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f8b23b5f20 Fix the adjustedodometer endpoint for decimal odometer multiplier 2024-09-25 21:02:49 -06:00
Hargata Softworks
b69749b073
Merge pull request #635 from hargata/Hargata/update.demo
Update demo defaults.
2024-09-24 22:35:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
951eea844f Updated demo default 2024-09-24 22:35:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3e5aff0c9 Updated demo file 2024-09-24 22:33:00 -06:00
Hargata Softworks
ef3e450e4f
Merge pull request #634 from hargata/Hargata/api.enhancement.dos
Added documentation and new API endpoint to calculate adjusted odometer.
2024-09-24 22:05:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8272fa20da Added documentation and new API endpoint to calculate adjusted odometer. 2024-09-24 22:04:31 -06:00
Hargata Softworks
9ef0a748a3
Merge pull request #633 from hargata/Hargata/563.619
added flag for optional odometer.
2024-09-24 19:20:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6de928aef4 added flag for optional odometer. 2024-09-24 19:18:13 -06:00
Hargata Softworks
34ff874949
Merge pull request #632 from hargata/Hargata/563.619
make odometer optional
2024-09-24 18:43:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a653f3ac7f make odometer optional 2024-09-24 18:36:59 -06:00
Hargata Softworks
dc96084406
Merge pull request #631 from hargata/Hargata/630
Fix light mode bug.
2024-09-24 18:30:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e056aaa7dc Fix light mode bug. 2024-09-24 18:11:50 -06:00
Hargata Softworks
73bb3c11b4
Merge pull request #628 from hargata/Hargata/adaptive.color.mode
fix label.
2024-09-23 08:37:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c88a040728 fix label. 2024-09-23 08:36:58 -06:00
Hargata Softworks
dfa56c2b12
Merge pull request #627 from hargata/Hargata/adaptive.color.mode
Added switch for adaptive color mode.
2024-09-23 08:34:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
104d4bab52 Added switch for adaptive color mode. 2024-09-23 08:31:54 -06:00
Hargata Softworks
6916161e87
Merge pull request #626 from hargata/Hargata/info.endpoint.update
#482 - Allow Root Users to Login via OIDC
2024-09-22 14:36:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
512852d217 added flag to enable root user to login via OIDC 2024-09-22 14:32:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a61c699417 Added PlanRecords to info endpoint 2024-09-22 13:48:01 -06:00
Hargata Softworks
1e832f014f
Merge pull request #625 from hargata/Hargata/update.links
Updated documentation links and removed static documentation.
2024-09-22 10:42:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e9a463e2e5 Updated documentation links and removed static documentation. 2024-09-22 10:41:36 -06:00
Hargata Softworks
d0c19d0436
Merge pull request #624 from hargata/Hargata/618
#618
2024-09-22 10:24:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3428abf358 code clean up for default reminder email and added flag to disable registration on front end. 2024-09-22 10:20:25 -06:00
Hargata Softworks
e6857331e2
Merge pull request #617 from hargata/Hargata/603
adaptive color mode if dark mode is not enabled.
2024-09-13 14:24:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1fd93d2efe update variable name 2024-09-13 14:24:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
84e44acbfe adaptive color mode if dark mode is not enabled. 2024-09-13 07:51:32 -06:00
Hargata Softworks
77014c71f2
Merge pull request #614 from hargata/Hargata/517
Add Default Reminder Email Recipient
2024-09-12 11:15:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2db01aa4ad reworded response message 2024-09-12 10:00:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6c3fa21cc5 Added environment variable to configure default reminder email recipient. Fixed bug with due date in email body and improved logging and error catching when sending emails. 2024-09-12 09:43:52 -06:00
Hargata Softworks
26b7d101ab
Merge pull request #610 from hargata/Hargata/609
Added custom thresholds for reminder.
2024-09-07 13:37:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3c3cdf0cb Added custom thresholds for reminder. 2024-09-07 08:39:26 -06:00
Hargata Softworks
e62fa31485
Merge pull request #608 from hargata/Hargata/606
See #606
2024-09-05 13:19:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5f283c1558 uh 2024-09-05 13:11:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
52163098d2 See #606 2024-09-05 13:04:16 -06:00
Hargata Softworks
dfe27f0577
Merge pull request #605 from hargata/Hargata/598
Added colored icons for Planner items.
2024-09-04 09:23:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e415a3468f Added colored icons for Planner items. 2024-09-03 20:13:07 -06:00
Hargata Softworks
71302a52e4
Merge pull request #602 from hargata/Hargata/light.mode.menu.fix
Fix menu color when in light mode.
2024-09-02 11:41:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
762730eb0f Fix menu color when in light mode. 2024-09-02 11:40:11 -06:00
Hargata Softworks
3999a8a54d
Merge pull request #601 from hargata/Hargata/div.zero.hotfix
Fix divide by zero error in Cost Per Mile metric
2024-09-01 11:02:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
065291e146 Fix divide by zero error in Cost Per Mile metric 2024-09-01 11:02:04 -06:00
Hargata Softworks
99376aba1c
Merge pull request #600 from hargata/Hargata/537
Add user-configurable dashboard metric
2024-09-01 10:48:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
698e8a0a95 fix default placeholder label for data table. 2024-08-31 23:50:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b1b3a127dc fix bug with data table. 2024-08-31 20:28:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec55c95c71 Allow users to set which dashboard metric they want to see in the garage. 2024-08-31 20:28:32 -06:00
Hargata Softworks
e6eb2b473c
Merge pull request #599 from hargata/Hargata/551
Added data table
2024-08-31 17:36:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a4b39820e4 updated label. 2024-08-31 10:39:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4a20c81047 convert to number of days and replace zero values with dashes 2024-08-31 10:33:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b72ab461e5 revert and enhance. 2024-08-31 09:46:08 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0be498d9bd Added data table 2024-08-30 15:29:09 -06:00
Hargata Softworks
00bad986e5
Merge pull request #596 from hargata/Hargata/reminder.tooltip.fix
Stats API Endpoint
2024-08-29 08:19:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4710e84dcf more stats. 2024-08-27 12:20:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ee55f8c884 added vehicle info API endpoint for dashboards and whatnot. 2024-08-27 12:04:57 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3a74116e70 Do not load tooltips on touch screen only device. 2024-08-27 11:07:59 -06:00
Hargata Softworks
cfd83b5b4e
Merge pull request #594 from hargata/Hargata/588
removed order by
2024-08-26 15:25:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e468261095 removed order by 2024-08-26 15:23:20 -06:00
Hargata Softworks
1362dc87df
Merge pull request #593 from hargata/Hargata/588
Allow extra fields to be added via API
2024-08-26 15:13:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b89902bbdb Allow extra fields to be added via API 2024-08-26 15:12:05 -06:00
Hargata Softworks
f31b70a6dc
Merge pull request #592 from hargata/Hargata/531
Display the due date and odometer in a tooltip in the reminder record…
2024-08-26 12:43:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
47a1f0c4d5 Display the due date and odometer in a tooltip in the reminder records view. 2024-08-26 12:38:42 -06:00
Hargata Softworks
3234b530d5
Merge pull request #591 from hargata/Hargata/reminder.api.enhancement
Add due date and due odometer to reminder output for API method.
2024-08-26 10:33:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
75d16544db Add due date and due odometer to reminder output for API method. 2024-08-26 10:32:14 -06:00
Hargata Softworks
7179b60116
Merge pull request #590 from hargata/Hargata/csv.extrafield.export
remove unused variable.
2024-08-23 13:45:22 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7cb5d8254f remove unused variable. 2024-08-23 13:44:58 -06:00
Hargata Softworks
6ab7ee6e4f
Merge pull request #589 from hargata/Hargata/csv.extrafield.export
Add functionality to export extra fields in CSV exports.
2024-08-23 13:41:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a57ce55f8b Fix type 2024-08-23 13:41:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
190484762d Add functionality to export extra fields in CSV exports. 2024-08-23 13:39:08 -06:00
Hargata Softworks
78554ade5d
Merge pull request #587 from hargata/Hargata/api.enhancement
Further Enhance Cleanup API Endpoint.
2024-08-22 11:13:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
364a13226a Added return object. 2024-08-22 11:11:17 -06:00
Hargata Softworks
691813838c
Merge pull request #586 from hargata/Hargata/cleanup.api
Add API Controller method to clean up temp files and unlinked thumbnails
2024-08-21 21:24:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0defb0fadf json json. 2024-08-21 21:23:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
08bf00ee37 Add API Controller method to clean up temp files and unlinked thumbnails. 2024-08-21 21:21:24 -06:00
Hargata Softworks
522322ee2a
Merge pull request #585 from hargata/Hargata/570
Upload files on paste.
2024-08-21 18:13:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
062e3600e7 update uploaded files in real time for records that already have uploaded files and fix note attachment bug 2024-08-21 18:11:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a92160f6d7 Upload files on paste. 2024-08-21 14:38:15 -06:00
Hargata Softworks
4adf967f7f
Merge pull request #584 from hargata/Hargata/534
Add fuel type dropdown and diesel fuel type.
2024-08-19 12:13:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fd6bd98a25 Missing translation key. 2024-08-19 12:12:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fe76f32778 Add fuel type dropdown and diesel fuel type. 2024-08-19 12:09:50 -06:00
Hargata Softworks
897b4f2efe
Merge pull request #581 from hargata/Hargata/576
Resolve Empty Tables Postgres Bug.
2024-08-14 09:27:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
267f903ffe Resolve bug where attempting to delete a vehicle with zero records returns an error. 2024-08-13 08:19:05 -06:00
Hargata Softworks
db884d0a6a
Merge pull request #569 from hargata/Hargata/568
Add PKCE to OIDC Auth Flow
2024-07-31 12:28:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d1190e7ddb Update readme to add helm chart by anza labs. 2024-07-31 12:27:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dae8ab679e add better logging for when IdP returns errors. 2024-07-30 11:57:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
86ec9409c3 added pkce code for when user logins automatically. 2024-07-29 15:28:00 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2c4ddb0c38 Updated version. 2024-07-29 15:23:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44d10f11ca Add PKCE functionality(RFC 7636) to OIDC functionality. 2024-07-29 15:23:10 -06:00
Hargata Softworks
6cf733b9c6
Merge pull request #558 from hargata/Hargata/545
enable extra fields to be imported via CSV
2024-07-09 14:52:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1eb6e2cedf enable extra fields to be imported via CSV 2024-07-09 14:48:09 -06:00
Hargata Softworks
ef4deaba8f
Merge pull request #557 from hargata/Hargata/512
recurring interval are now based on reminder metric.
2024-07-09 11:16:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e8c196c2fa recurring interval are now based on reminder metric. 2024-07-09 11:15:02 -06:00
Hargata Softworks
f7c9db6353
Merge pull request #556 from hargata/Hargata/mailconfig.cleanup
Clean up MailConfig and update npsql
2024-07-09 10:35:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4ce720ff97 Update npgSQL 2024-07-09 10:34:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a96011629b clean up mailconfig 2024-07-09 10:32:40 -06:00
Hargata Softworks
9dcdcf97e8
Merge pull request #508 from snpaul22/main
Create app schema automatically
2024-07-06 17:40:13 -06:00
snpaul22
5148338f52
Merge branch 'hargata:main' into main 2024-07-06 18:54:14 -04:00
Hargata Softworks
0d3c04d8f8
Merge pull request #554 from hargata/Hargata/root.user.update
Make it easier to update root user credentials and patch bug.
2024-07-05 11:26:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
15328a14b4 Make it easier to update root user credentials and patch bug. 2024-07-05 11:18:46 -06:00
Hargata Softworks
2d092f722a
Merge pull request #550 from hargata/Hargata/disabled.init.odo
updated button color.
2024-07-02 15:05:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8825cb9b9b updated button color. 2024-07-02 15:05:16 -06:00
Hargata Softworks
2f17e303ab
Merge pull request #549 from hargata/Hargata/disabled.init.odo
Disable the initial odometer reading field
2024-07-02 11:51:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4b56c8a343 a better implementation of disabled attribute 2024-07-02 11:09:41 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7b6b62c623 Disable the initial odometer reading field if it's non-zero to prevent accidental editing. 2024-07-02 10:54:11 -06:00
Hargata Softworks
07f5e66491
Merge pull request #519 from NateWright/main
Add maskable icons for Android PWA
2024-06-18 08:25:49 -06:00
Hargata Softworks
fe633f3220
Merge pull request #542 from kapcake/patch-1
Update money regex to allow more than 6 figures on integer part
2024-06-18 08:25:00 -06:00
kapcake
af1090553f
Update money regex to not allow unlimited figures
The regex now allows up to 8 groups of 3 digits on the integer part, which is within the bounds of C# decimal, preventing the user to go out of bounds on the form input.
2024-06-18 15:14:26 +02:00
kapcake
92c2e66660
Update money regex to allow more than 6 figures on integer part 2024-06-18 15:02:20 +02:00
Nathan Wright
08372f9dcb
Merge branch 'hargata:main' into main 2024-06-08 15:20:31 -04:00
Hargata Softworks
64ea0e2eee
Merge pull request #533 from hargata/Hargata/532
add support for smtp clients that requires no authentication.
2024-05-31 09:11:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7ab476a88f add support for smtp clients that requires no authentication. 2024-05-31 09:11:09 -06:00
Hargata Softworks
de41ca911d
Merge pull request #530 from hargata/Hargata/527
Minor Bug Fixes
2024-05-29 09:36:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dde9688f96 Updated supplies usage validation to allow for free supplies. 2024-05-28 14:54:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c1ca63edc0 removed unnecessary tostring 2024-05-28 14:26:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cbc430499f added new currency formatting function 2024-05-28 14:14:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4da9fa4802 See 514 2024-05-28 13:55:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
42586d9556 Updated version number 2024-05-28 12:56:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ea4387d4ab Add missing translation keys 2024-05-28 12:53:20 -06:00
Hargata Softworks
0707b515ab
Merge pull request #525 from hargata/Hargata/sponsor.tiers
Updated Sponsors File Path
2024-05-21 15:42:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78ae71fc46 Updated Sponsors File Path 2024-05-21 15:41:05 -06:00
Hargata Softworks
3f62cd40e7
Merge pull request #524 from hargata/Hargata/sponsor.tiers
Add Sponsor Section
2024-05-21 15:32:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
47657c0093 Added sponsor section. 2024-05-21 15:29:46 -06:00
Hargata Softworks
a5b0fde4b6
Merge pull request #522 from hargata/Hargata/swal.uncensored
Hargata/swal.uncensored
2024-05-20 12:26:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78cc0b34b1 Updated version number 2024-05-20 12:25:07 -06:00
Hargata Softworks
e3017e986b
Merge pull request #520 from hargata/Hargata/license.change
Removed commercial license restrictions from license.
2024-05-20 12:22:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e12cd876db removed unused files 2024-05-20 12:21:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5292e4b814 utilize uncensored version of sweet alert 2024-05-20 12:16:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dbdd16ab89 Removed commercial license restrictions from license. 2024-05-14 08:46:47 -06:00
nwright
61c2600286 added maskable icons 2024-05-13 19:34:03 -04:00
snpaul22
163a33ae3a
Create init.sql
Script checks for "app" schema in Postgres during startup. It will create the schema automatically if it doesn't exist or skip if it does exist.
2024-05-04 11:27:50 -04:00
snpaul22
37d064aa62
Update docker-compose.postgresql.yml
Added Postgres volume bind for initialization script
2024-05-04 11:23:55 -04:00
Hargata Softworks
ddc3c2e1b5
Merge pull request #503 from hargata/Hargata/null.mail.config
null check for mail config
2024-04-25 12:45:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
49184b287b null check for mail config 2024-04-25 12:45:21 -06:00
Hargata Softworks
f6139bda0d
Merge pull request #502 from hargata/Hargata/mailkit.upgrade
fix inefficiencies
2024-04-25 11:33:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a6471b823b fix inefficiencies 2024-04-25 11:11:54 -06:00
Hargata Softworks
7c34003647
Merge pull request #501 from hargata/Hargata/mailkit.upgrade
MailKit Upgrade
2024-04-25 10:46:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1aa21f9980 Uprgade from .NET SMTPClient to MailKit as the default smtpclient does not support modern protocols. 2024-04-25 10:45:55 -06:00
Hargata Softworks
ce4ca50939
Merge pull request #499 from hargata/Hargata/persist.metric.bug
fix getyear method
2024-04-23 23:50:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fb28260c4a fix getyear method 2024-04-23 23:50:06 -06:00
Hargata Softworks
626a904747
Merge pull request #498 from hargata/Hargata/persist.metric.bug
Minor bug fix
2024-04-23 23:43:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
893cdafdc5 Fixes a very minor bug where the persisted year metric blanks out when viewing a different car that doesn't have that specific year. 2024-04-23 23:42:09 -06:00
Hargata Softworks
dbfb7d7d9c
Merge pull request #493 from hargata/Hargata/persist.dashboard
check against null instead of undefined,
2024-04-15 08:42:43 -06:00
DESKTOP-GENO133\IvanPlex
a66538a7db check against null instead of undefined, 2024-04-15 08:41:53 -06:00
Hargata Softworks
2f77d87d4f
Merge pull request #492 from hargata/Hargata/persist.dashboard
temporarily persist dashboard metrics in sessionStorage
2024-04-15 08:25:54 -06:00
DESKTOP-GENO133\IvanPlex
de85ba984c temporarily persist dashboard metrics in sessionStorage 2024-04-15 08:20:37 -06:00
Hargata Softworks
caac1a05ae
Merge pull request #480 from hargata/Hargata/fix.link.color
fix donation link colors
2024-04-12 08:04:44 -06:00
Hargata Softworks
eb5793b819
Merge pull request #490 from hargata/Hargata/fix.alt.fuel
Fix Alternate Fuel Mileage Bug
2024-04-12 08:04:28 -06:00
DESKTOP-GENO133\IvanPlex
5ef3e1e2ce updated version number 2024-04-12 08:04:04 -06:00
DESKTOP-GENO133\IvanPlex
d8b459e5ee persist regional formatting when viewing record statistics. 2024-04-12 08:01:25 -06:00
DESKTOP-GENO133\IvanPlex
7b40d58aa1 fix alternate fuel mileage auto converting to NA decimal format bug. 2024-04-12 07:54:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
809e9b838e fix donation link colors 2024-04-09 21:29:19 -06:00
Hargata Softworks
23ae36ebd9
Merge pull request #475 from hargata/Hargata/update.readme
check if function exists.
2024-04-07 21:03:31 -06:00
DESKTOP-GENO133\IvanPlex
9c3f7d20f5 fix plans not updating when deleted. 2024-04-07 21:01:06 -06:00
DESKTOP-GENO133\IvanPlex
083298303c check if function exists. 2024-04-07 20:51:55 -06:00
Hargata Softworks
224970a07e
Merge pull request #474 from hargata/Hargata/update.readme
Fix the problem with time.
2024-04-07 19:15:41 -06:00
DESKTOP-GENO133\IvanPlex
86d039e5b0 Fix the problem with time. 2024-04-07 17:59:08 -06:00
Hargata Softworks
6a8038aac9
Merge pull request #473 from hargata/Hargata/update.readme
update readme
2024-04-07 13:59:02 -06:00
DESKTOP-GENO133\IvanPlex
e748f08a8e update readme 2024-04-07 13:58:32 -06:00
Hargata Softworks
3580963e9f
Merge pull request #472 from hargata/Hargata/planner.improvement.part2
Improved UI UX For Planner Edit
2024-04-07 12:51:28 -06:00
DESKTOP-GENO133\IvanPlex
b008ce2ab8 Improved UI UX 2024-04-07 12:50:28 -06:00
Hargata Softworks
ea0c2c7061
Merge pull request #471 from hargata/Hargata/transl
Updated translation
2024-04-07 11:26:35 -06:00
DESKTOP-GENO133\IvanPlex
0926220933 Updated translation 2024-04-07 11:26:12 -06:00
Hargata Softworks
ca975bbdd3
Merge pull request #470 from hargata/Hargata/planner.template.edit
updated translation
2024-04-07 11:22:20 -06:00
DESKTOP-GENO133\IvanPlex
b16c5c5302 updated translation 2024-04-07 11:21:43 -06:00
Hargata Softworks
cad05fe5d9
Merge pull request #466 from hargata/Hargata/planner.improvement
Moved template modal outside of add modal.
2024-04-07 11:20:42 -06:00
Hargata Softworks
a7cd466d9c
Merge pull request #469 from hargata/Hargata/planner.template.edit
Add functionality to let users edit plan record templates
2024-04-07 11:20:28 -06:00
DESKTOP-GENO133\IvanPlex
4472a67ec0 fixed label function 2024-04-07 11:19:52 -06:00
DESKTOP-GENO133\IvanPlex
c84a4029ec basic functionality to edit templates 2024-04-07 11:09:05 -06:00
DESKTOP-GENO133\IvanPlex
d7d9ab505e Add functionality to let users edit plan record templates 2024-04-07 08:31:17 -06:00
DESKTOP-GENO133\IvanPlex
c582f5f5c7 Moved template modal outside of add modal. 2024-04-05 19:36:26 -06:00
Hargata Softworks
4c30939339
Merge pull request #465 from hargata/Hargata/unused.translation.key
Updated translation file and unsaved changes label color.
2024-04-05 18:50:02 -06:00
DESKTOP-GENO133\IvanPlex
cecd6a1d2b Updated translation file and unsaved changes label color. 2024-04-05 18:48:54 -06:00
Hargata Softworks
853dcbb364
Merge pull request #464 from hargata/Hargata/unused.translation.key
removed cached translation key
2024-04-05 07:18:08 -06:00
DESKTOP-GENO133\IvanPlex
ae327ed26d removed cached translation key 2024-04-05 07:17:49 -06:00
DESKTOP-GENO133\IvanPlex
3e416aa255 Revert "removed unused translation key"
This reverts commit ab98d02106faceaca1c6c76b8f0de7bf3e28cebe.
2024-04-05 07:16:11 -06:00
DESKTOP-GENO133\IvanPlex
61c2c3fc83 removed unused translation key 2024-04-05 07:16:10 -06:00
Hargata Softworks
ca749aaf1e
Merge pull request #463 from hargata/Hargata/unsaved.changes
updated cached view to only show when there are unsaved changes present.
2024-04-05 07:11:50 -06:00
DESKTOP-GENO133\IvanPlex
2a2cb3bd0c updated cached view to only show when there are unsaved changes present. 2024-04-05 07:10:09 -06:00
Hargata Softworks
44e3d19844
Merge pull request #462 from hargata/Hargata/global.search
added incremental search
2024-04-04 19:16:22 -06:00
DESKTOP-GENO133\IvanPlex
1e25fffc70 added incremental search 2024-04-04 19:14:49 -06:00
Hargata Softworks
44645ed23d
Merge pull request #461 from hargata/Hargata/global.search
added global search
2024-04-04 10:48:57 -06:00
DESKTOP-GENO133\IvanPlex
fa557e5f76 added global search 2024-04-04 10:46:43 -06:00
Hargata Softworks
7202fda38e
Merge pull request #458 from hargata/Hargata/cached.records
Cached Record.
2024-04-02 21:53:46 -06:00
DESKTOP-GENO133\IvanPlex
d05afe41d6 Cache the record the user was viewing if they didn't save, delete, or move it. 2024-04-02 21:51:02 -06:00
Hargata Softworks
bfc0b58728
Merge pull request #453 from hargata/Hargata/bring.back.modal
Allow users to bring back modal window they accidentally closed.
2024-04-02 07:39:25 -06:00
DESKTOP-GENO133\IvanPlex
22e8aaca81 Allow users to bring back modal window they accidentally closed. 2024-04-02 07:29:16 -06:00
Hargata Softworks
5cb1247fb2
Merge pull request #452 from hargata/Hargata/odometer.increment
Odometer Increments
2024-04-01 18:31:27 -06:00
DESKTOP-GENO133\IvanPlex
17a6d99703 Allow users to increment for last reported odometer reading when creating new records. 2024-04-01 18:25:20 -06:00
Hargata Softworks
3e7917f767
Create FUNDING.yml 2024-04-01 09:14:46 -06:00
Hargata Softworks
e9277d4dd9
Merge pull request #443 from hargata/Hargata/reminder.icons
reminders will now display urgency in icons instead of a full badge w…
2024-03-29 09:35:17 -06:00
DESKTOP-GENO133\IvanPlex
058edd8af6 reminders will now display urgency in icons instead of a full badge when viewed in small screens. 2024-03-29 08:15:55 -06:00
Hargata Softworks
8ed7dcb9ff
Merge pull request #441 from hargata/Hargata/reminder.mobile
Improved Reminder page usability on mobile
2024-03-28 19:15:49 -06:00
DESKTOP-GENO133\IvanPlex
1f4827abc0 Improved Reminder page usabilty on mobile 2024-03-28 19:13:19 -06:00
Hargata Softworks
9715a0fcf7
Merge pull request #440 from hargata/Hargata/vehicle.garage.tags
Added status tags
2024-03-27 20:22:44 -06:00
DESKTOP-GENO133\IvanPlex
9bf475c352 Added status tags 2024-03-27 20:16:56 -06:00
Hargata Softworks
c2aeb4bca0
Merge pull request #438 from hargata/Hargata/garage.status
Hargata/garage.status
2024-03-27 11:41:43 -06:00
DESKTOP-GENO133\IvanPlex
07b3020999 use current mileage and date when renewing reminders. 2024-03-27 11:39:30 -06:00
DESKTOP-GENO133\IvanPlex
c2a7f39025 add depreciation calculation 2024-03-27 09:24:33 -06:00
DESKTOP-GENO133\IvanPlex
a92d422972 add purchase and sold price 2024-03-27 08:21:02 -06:00
Hargata Softworks
345eb65c3a
Merge pull request #437 from hargata/Hargata/copy.supplies.attachments
Option to copy supplies attachments into records.
2024-03-26 18:33:15 -06:00
DESKTOP-GENO133\IvanPlex
a459973983 Add functionality to copy over attachments from supplies to records that are utilizing them. 2024-03-26 18:23:16 -06:00
Hargata Softworks
470dd4d78a
Merge pull request #435 from hargata/Hargata/reminder.tag
Reminder Tags
2024-03-26 08:47:14 -06:00
DESKTOP-GENO133\IvanPlex
e4cb183140 improve readability on urgent reminder labels, filtering by tag now updates aggregate counts. 2024-03-26 08:40:30 -06:00
DESKTOP-GENO133\IvanPlex
6455af96bf Add tag functionality to reminders. 2024-03-26 08:11:14 -06:00
Hargata Softworks
42afa87464
Merge pull request #432 from hargata/Hargata/multiple.reminders
added translation layer
2024-03-25 06:46:59 -06:00
DESKTOP-GENO133\IvanPlex
97466eeff2 added translation layer 2024-03-25 06:45:15 -06:00
Hargata Softworks
bcbfd4ba9c
Merge pull request #426 from hargata/Hargata/multiple.reminders
Select Multiple Reminders when creating new record.
2024-03-23 20:55:04 -06:00
DESKTOP-GENO133\IvanPlex
cfa052fc31 Fix styling 2024-03-23 20:52:46 -06:00
DESKTOP-GENO133\IvanPlex
3f71f6a8d8 Updated version number. 2024-03-23 20:35:41 -06:00
DESKTOP-GENO133\IvanPlex
b70e442ca3 Allow users to select multiple reminders to push back when creating new record. 2024-03-23 20:32:23 -06:00
Hargata Softworks
1f60b2aadc
Merge pull request #422 from hargata/Hargata/auto.oidc
Enable auto login via OIDC
2024-03-20 14:55:14 -06:00
DESKTOP-GENO133\IvanPlex
3d2117ddaf Enable auto login via OIDC 2024-03-20 14:53:51 -06:00
Hargata Softworks
bcff18ea58
Merge pull request #421 from hargata/Hargata/extrafield.links
Add URL parser to extra fields.
2024-03-20 14:35:12 -06:00
DESKTOP-GENO133\IvanPlex
2e9821402f Add URL parser to extra fields. 2024-03-20 13:54:51 -06:00
Hargata Softworks
8914c5cd51
Merge pull request #420 from hargata/Hargata/litedb.concurrency
Hargata/litedb.concurrency
2024-03-20 13:14:24 -06:00
DESKTOP-GENO133\IvanPlex
0b240498f9 Renamed LiteDBInjection to LiteDBHelper 2024-03-20 13:13:24 -06:00
DESKTOP-GENO133\IvanPlex
16f66364cf fix backup breaking error. 2024-03-20 13:04:15 -06:00
Hargata Softworks
f2b0cec427
Merge pull request #419 from hargata/Hargata/litedb.concurrency
Added DB Checkpoint for WAL
2024-03-20 11:44:23 -06:00
DESKTOP-GENO133\IvanPlex
fac05ff5c0 Added DB Checkpoint records. 2024-03-20 11:40:55 -06:00
Hargata Softworks
1ac6dfd2a6
Merge pull request #418 from hargata/Hargata/litedb.concurrency
LiteDB Concurrency
2024-03-20 10:44:39 -06:00
DESKTOP-GENO133\IvanPlex
8bcac7344f removed unused using. 2024-03-20 10:40:53 -06:00
DESKTOP-GENO133\IvanPlex
300c986abb Replaced LiteDB's initialization with every DB call with Dependency Injection instead. 2024-03-20 10:38:32 -06:00
Hargata Softworks
91a5f92df6
Merge pull request #415 from hargata/Hargata/cleanup
Fix security vulnerability with hosted files being accessible by unau…
2024-03-18 10:10:28 -06:00
DESKTOP-GENO133\IvanPlex
d60a09d48f Fix security vulnerability with hosted files being accessible by unauthorized users.
Note that this is a retrospecitve commit to credit the user with finding the vulnerability.

Co-authored-by: Julien Stebenne <julien.stebenne@gmail.com>
2024-03-18 10:08:34 -06:00
Hargata Softworks
b22bb7c7ad
Merge pull request #414 from hargata/Hargata/cleanup
Hargata/cleanup
2024-03-18 09:21:56 -06:00
DESKTOP-GENO133\IvanPlex
63cddc4ab0 fixed security vulnerability. 2024-03-18 09:18:05 -06:00
DESKTOP-GENO133\IvanPlex
790061d5c4 cleaned up unused usings. 2024-03-17 18:33:05 -06:00
Hargata Softworks
6e4e2795b6
Merge pull request #406 from hargata/Hargata/multi.gas.edit
fixed ui bug
2024-03-16 12:24:55 -06:00
DESKTOP-GENO133\IvanPlex
d4e51b714d fixed ui bug 2024-03-16 12:23:46 -06:00
Hargata Softworks
6a164dc60b
Merge pull request #405 from hargata/Hargata/multi.gas.edit
Added functionality to edit multiple gas records.
2024-03-16 12:11:52 -06:00
DESKTOP-GENO133\IvanPlex
ce602dcf66 Added functionality to edit multiple gas records. 2024-03-16 12:10:40 -06:00
Hargata Softworks
2facb1ab46
Merge pull request #402 from hargata/Hargata/further.improvements
More enhancements
2024-03-16 10:04:03 -06:00
DESKTOP-GENO133\IvanPlex
3c7d575c85 fixes and ensure that users no longer have to hard refresh their sites. 2024-03-16 10:01:48 -06:00
DESKTOP-GENO133\IvanPlex
9635c3c2c5 fixed bug where translations are not being backed up. 2024-03-16 09:44:27 -06:00
DESKTOP-GENO133\IvanPlex
72427fc19d fixed average calculation 2024-03-15 17:15:05 -06:00
DESKTOP-GENO133\IvanPlex
0bbd3c5491 fixed spelling 2024-03-15 17:09:51 -06:00
DESKTOP-GENO133\IvanPlex
871de4e75a updated translation 2024-03-15 17:07:01 -06:00
DESKTOP-GENO133\IvanPlex
bf984f280e added feature to make odometer adjustments a piece of cake. 2024-03-15 16:48:23 -06:00
DESKTOP-GENO133\IvanPlex
2b8f3cf13a properly capitalized web hook descriptions. 2024-03-15 16:21:41 -06:00
Hargata Softworks
1630a5c9ec
Merge pull request #401 from hargata/Hargata/webhook
added web hooks
2024-03-15 15:12:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f17faa33f4 fixed punctuation 2024-03-15 15:09:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c245b848a0 reminder hooks 2024-03-15 15:06:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0ead9112c6 added vehicle deets 2024-03-15 15:01:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44da393369 added web hooks 2024-03-15 14:59:30 -06:00
Hargata Softworks
5ae1628b7c
Merge pull request #399 from hargata/Hargata/odo.modifier
Odometer Modifier
2024-03-15 14:05:41 -06:00
DESKTOP-GENO133\IvanPlex
59511d9ddd Updated version number 2024-03-15 12:49:34 -06:00
DESKTOP-GENO133\IvanPlex
82b0fba99a added functionality to modify odometer value when adding new odometer records. 2024-03-15 12:49:02 -06:00
DESKTOP-GENO133\IvanPlex
618107e515 added fields for odometer adjustments. 2024-03-15 10:16:28 -06:00
DESKTOP-GENO133\IvanPlex
35f931adf2 additional fields for vehicle level odometer settings. 2024-03-14 23:17:48 -06:00
Hargata Softworks
4a1e41cd54
Merge pull request #393 from hargata/Hargata/multi.odo.edit
Added functionality to edit multiple odometer records.
2024-03-13 10:18:13 -06:00
DESKTOP-T0O5CDB\DESK-555BD
888ec5cbbe Added functionality to edit multiple odometer records. 2024-03-13 10:16:04 -06:00
Hargata Softworks
f8f044d0cc
Merge pull request #391 from hargata/Hargata/odo.changes
width issue
2024-03-11 11:42:26 -06:00
DESKTOP-GENO133\IvanPlex
36148c5539 width issue 2024-03-11 11:41:53 -06:00
Hargata Softworks
030dde0d64
Merge pull request #390 from hargata/Hargata/odo.changes
fixed admin panel text and button spacing.
2024-03-11 11:40:28 -06:00
DESKTOP-GENO133\IvanPlex
99faa951f9 fixed admin panel text and button spacing. 2024-03-11 11:39:56 -06:00
Hargata Softworks
7fb3a80ea3
Merge pull request #389 from hargata/Hargata/odo.changes
added support for meta key for MacOS users
2024-03-11 10:37:40 -06:00
DESKTOP-GENO133\IvanPlex
f277048aa9 added method to force recalculate distances 2024-03-11 10:34:42 -06:00
DESKTOP-GENO133\IvanPlex
34e3b8e145 added support for meta key for MacOS users 2024-03-11 10:01:24 -06:00
Hargata Softworks
91e8526b8e
Merge pull request #388 from hargata/Hargata/odo.changes
removed logic that can cause bad data.
2024-03-10 14:19:54 -06:00
DESKTOP-GENO133\IvanPlex
926188ef64 removed logic that can cause bad data. 2024-03-10 14:18:24 -06:00
Hargata Softworks
1b5fc6729e
Merge pull request #387 from hargata/Hargata/odo.changes
Odometer Changes
2024-03-10 14:13:35 -06:00
DESKTOP-GENO133\IvanPlex
3ac53ea144 added method to calculate distance. 2024-03-10 11:59:38 -06:00
DESKTOP-GENO133\IvanPlex
4dc3b4f741 fixed csv import 2024-03-10 09:05:15 -06:00
DESKTOP-GENO133\IvanPlex
7c1e6bd45c made API endpoints copyable 2024-03-10 08:56:41 -06:00
DESKTOP-GENO133\IvanPlex
00b3145e79 display distance traveled. 2024-03-10 08:02:44 -06:00
DESKTOP-GENO133\IvanPlex
def9a7770f odometer to trips improvement. 2024-03-10 06:33:39 -06:00
Hargata Softworks
5ba0bd5771
Merge pull request #381 from hargata/Hargata/fix.print
7z
2024-03-08 16:19:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
adc2de07f7 7z 2024-03-08 16:19:36 -07:00
Hargata Softworks
e12f528635
Merge pull request #380 from hargata/Hargata/fix.print
fixed the date column.
2024-03-08 16:17:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c96ac53b91 fixed the date column. 2024-03-08 16:17:20 -07:00
Hargata Softworks
8c15bd5534
Merge pull request #378 from hargata/Hargata/fix.print
Improved Attachment Export
2024-03-08 11:49:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90755d68b3 Improved Attachment Export 2024-03-08 11:49:30 -07:00
Hargata Softworks
94dfe9a0bb
Merge pull request #377 from hargata/Hargata/fix.print
Added flex shrink
2024-03-08 11:13:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9dbcf71856 Added flex shrink 2024-03-08 11:11:37 -07:00
Hargata Softworks
728fc3593f
Merge pull request #374 from hargata/Hargata/fix.print
Fixed black border issue when printing
2024-03-07 22:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1347585af2 Fixed black border issue when printing 2024-03-07 22:49:11 -07:00
Hargata Softworks
920de489be
Merge pull request #372 from hargata/Hargata/delta.stats
Added MOTD.
2024-03-07 08:46:14 -07:00
DESKTOP-GENO133\IvanPlex
19cb964543 Added MOTD. 2024-03-07 08:44:58 -07:00
Hargata Softworks
eb03979ad8
Merge pull request #369 from hargata/Hargata/delta.stats
Added Total and Average Cost
2024-03-06 07:44:28 -07:00
DESKTOP-GENO133\IvanPlex
ebe871b82a reduced operations 2024-03-06 07:42:46 -07:00
DESKTOP-GENO133\IvanPlex
04a103fdc0 column specific search 2024-03-06 07:40:18 -07:00
DESKTOP-GENO133\IvanPlex
d0f80d4150 Added Total and Average Cost 2024-03-05 08:43:10 -07:00
Hargata Softworks
48c388dea7
Merge pull request #368 from redge76/Sample_postgreSQL
Sample postgreSQL configuration
2024-03-04 17:13:02 -07:00
Redge
d05f8b1f84
Sample postgrSQL connection stringin file .env
add a sample connection string to enable postgrSQL database backend
2024-03-05 01:09:10 +01:00
Redge
167d6e607e
Sample docker-compose.postgresql.yml 2024-03-05 01:05:24 +01:00
Hargata Softworks
8755c6379a
Merge pull request #367 from hargata/Hargata/record.delta.stats
Hargata/record.delta.stats
2024-03-04 16:20:24 -07:00
DESKTOP-GENO133\IvanPlex
1e2bd0ea4b Allow users to set their own reminder urgency threshold 2024-03-04 16:18:09 -07:00
DESKTOP-GENO133\IvanPlex
2cf93cf669 Added setting to hide sold vehicles. 2024-03-04 13:37:07 -07:00
Hargata Softworks
a18b81d52e
Merge pull request #363 from hargata/Hargata/record.delta.stats
Hargata/record.delta.stats
2024-03-03 21:10:14 -07:00
DESKTOP-GENO133\IvanPlex
604c98a031 allowed users to configure their own file extensions. 2024-03-03 19:32:20 -07:00
DESKTOP-GENO133\IvanPlex
940f48b816 Updated version number 2024-03-03 09:20:02 -07:00
DESKTOP-GENO133\IvanPlex
f81545178d Added statistics between records. 2024-03-03 09:09:42 -07:00
Hargata Softworks
a87b599bdf
Merge pull request #362 from hargata/Hargata/persist.columns
null check for column preferences
2024-03-02 19:13:11 -07:00
DESKTOP-GENO133\IvanPlex
2c45521fb4 null check for column preferences 2024-03-02 19:11:21 -07:00
Hargata Softworks
0870387995
Merge pull request #361 from hargata/Hargata/persist.columns
Allows users to inject root user credentials as environment variables.
2024-03-02 07:53:48 -07:00
DESKTOP-GENO133\IvanPlex
be281c78ff Allows users to inject root user credentials as environment variables. 2024-03-02 07:51:37 -07:00
Hargata Softworks
87c54335db
Merge pull request #360 from hargata/Hargata/persist.columns
authenticate root user via configHelper
2024-03-02 07:13:43 -07:00
DESKTOP-GENO133\IvanPlex
12e6c1677b null checks 2024-03-02 07:13:13 -07:00
DESKTOP-GENO133\IvanPlex
f69b789346 authenticate root user via configHelper 2024-03-02 07:11:54 -07:00
Hargata Softworks
9f05295f18
Merge pull request #359 from hargata/Hargata/persist.columns
revamped column check toggle behavior
2024-03-02 06:50:52 -07:00
DESKTOP-GENO133\IvanPlex
bf14e4c8c0 added functonality to persist column visibility 2024-03-02 06:49:40 -07:00
Hargata Softworks
bd4db09637
Merge pull request #357 from hargata/Hargata/responsive.table.columns
allow table columns to grow as needed.
2024-03-02 05:48:23 -07:00
DESKTOP-GENO133\IvanPlex
4b5dcb1c7e revamped column check toggle behavior 2024-03-02 05:47:22 -07:00
DESKTOP-GENO133\IvanPlex
b7f1ac5e8f allow table columns to grow as needed. 2024-03-02 04:57:45 -07:00
Hargata Softworks
bcf3c3dc33
Merge pull request #354 from hargata/Hargata/gas.search
deprecated pinned notes
2024-03-01 04:34:18 -07:00
DESKTOP-GENO133\IvanPlex
993c271fa8 deprecated pinned notes 2024-03-01 04:31:13 -07:00
Hargata Softworks
b8b545a072
Merge pull request #353 from hargata/Hargata/gas.search
Added Search Functionality to Gas Tab
2024-03-01 04:17:19 -07:00
DESKTOP-GENO133\IvanPlex
84b748a361 added more data points to vehicle history report. 2024-03-01 04:14:53 -07:00
DESKTOP-GENO133\IvanPlex
ef2b8cadc7 added notes as a toggleable column 2024-02-29 19:06:56 -07:00
DESKTOP-GENO133\IvanPlex
d8cfa2c397 Added gas search 2024-02-29 19:01:44 -07:00
Hargata Softworks
d8c7d63f21
Merge pull request #352 from hargata/Hargata/vehicle.extra.fields
Added Additional Fields for Vehicle
2024-02-29 17:20:50 -07:00
DESKTOP-GENO133\IvanPlex
3d3aa23a65 added sold and purchased date to vehicle. 2024-02-29 17:20:03 -07:00
DESKTOP-GENO133\IvanPlex
6993fe5df8 prevent overlap of additional fields on vehicle level vs record level. 2024-02-29 16:38:31 -07:00
Hargata Softworks
48d80a8460
Merge pull request #350 from hargata/Hargata/universal.search
Updated translation
2024-02-29 14:10:34 -07:00
DESKTOP-GENO133\IvanPlex
742c5b3489 Updated translation 2024-02-29 14:09:06 -07:00
Hargata Softworks
3cd75c02ee
Merge pull request #349 from hargata/Hargata/universal.search
Added Search Functionality
2024-02-29 13:57:55 -07:00
DESKTOP-GENO133\IvanPlex
854d5f0afe added search functionality to other tabs 2024-02-29 13:56:24 -07:00
DESKTOP-GENO133\IvanPlex
daabbfa759 Fixed null reference when no rows matches keyword. 2024-02-29 13:48:25 -07:00
DESKTOP-GENO133\IvanPlex
42ca4f76bf added a universal table search function 2024-02-29 13:31:32 -07:00
Hargata Softworks
6bf2801b1e
Merge pull request #348 from hargata/Hargata/show.extrafields
Show Extra Fields as Columns in tables.
2024-02-29 12:18:13 -07:00
DESKTOP-GENO133\IvanPlex
3be3a28cc9 moved js function to shared file so that it will work with shop supplies 2024-02-29 12:17:26 -07:00
DESKTOP-GENO133\IvanPlex
916e1640de added extra fields functionality to supplies 2024-02-29 12:15:37 -07:00
DESKTOP-GENO133\IvanPlex
ecb3b74581 added extra fields to odometer records 2024-02-29 11:59:55 -07:00
DESKTOP-GENO133\IvanPlex
2171c7fbe3 added extra fields functionality to tax 2024-02-29 11:53:05 -07:00
DESKTOP-GENO133\IvanPlex
4a909e5c44 Added extra fields functionality to fuel tab. 2024-02-29 11:36:54 -07:00
DESKTOP-GENO133\IvanPlex
c44c239b35 Added Extra Fields ability to Upgrades 2024-02-29 10:56:03 -07:00
DESKTOP-GENO133\IvanPlex
6990046a8d Added Extra Field Columns to Repair Records 2024-02-29 10:49:46 -07:00
DESKTOP-GENO133\IvanPlex
0750b080f6 added setting to hide extra field columns 2024-02-29 10:34:24 -07:00
DESKTOP-GENO133\IvanPlex
ca09d2ca66 more stuff 2024-02-29 10:18:56 -07:00
DESKTOP-GENO133\IvanPlex
ed3749eaf2 proof of concept for toggling column visibilities 2024-02-29 10:09:58 -07:00
DESKTOP-GENO133\IvanPlex
fa3c391ee9 added ability to show extra fields and their values. 2024-02-28 21:21:45 -07:00
Hargata Softworks
c6d945b8c0
Merge pull request #347 from hargata/Hargata/max.odo.api
Added API Endpoint to retrieve last reported odometer reading.
2024-02-28 20:13:18 -07:00
DESKTOP-GENO133\IvanPlex
9dc20abbb4 Added API Endpoint to retrieve last reported odometer reading. 2024-02-28 20:12:01 -07:00
Hargata Softworks
94cd543e56
Merge pull request #346 from hargata/Hargata/user.update
Added functionality for users to update their account information after signing up.
2024-02-28 16:31:16 -07:00
DESKTOP-GENO133\IvanPlex
4f50939eb2 Updated version number 2024-02-28 16:29:46 -07:00
DESKTOP-GENO133\IvanPlex
60f792b2ef added link in mobile nav 2024-02-28 16:29:19 -07:00
DESKTOP-GENO133\IvanPlex
383945b156 Updated translation file 2024-02-28 16:26:36 -07:00
DESKTOP-GENO133\IvanPlex
0e960715ee Delete token only if user save is successful and log user out after updating profile. 2024-02-28 16:20:01 -07:00
DESKTOP-GENO133\IvanPlex
522fd2a9f5 Added empty email address for root users. everything should be functional from here on out. 2024-02-28 15:46:59 -07:00
DESKTOP-GENO133\IvanPlex
9091b3d2a8 added frontend method to retrieve and send token to user's email address. 2024-02-28 15:02:54 -07:00
DESKTOP-GENO133\IvanPlex
acc3d2f6d0 Added email helper method to send token for user account update. 2024-02-28 14:33:10 -07:00
DESKTOP-GENO133\IvanPlex
a9d7ab0193 Added logging to OIDC Login flow. 2024-02-28 14:22:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cd720c34dd Updated logic 2024-02-28 14:18:12 -07:00
DESKTOP-GENO133\IvanPlex
c3839b1e98 added logic for updating user details. 2024-02-25 19:37:15 -07:00
Hargata Softworks
085eb2a9a0
Merge pull request #340 from hargata/Hargata/user.suggested.improvement
Bug Fixes
2024-02-24 16:57:17 -07:00
DESKTOP-GENO133\IvanPlex
f20c04d523 fixed bug in linux where oncontextmenu is fired before onrightclick. 2024-02-24 16:54:42 -07:00
DESKTOP-GENO133\IvanPlex
b7b7d6ad3e fixed tags input on android device. 2024-02-24 16:39:26 -07:00
Hargata Softworks
299444d767
Merge pull request #338 from hargata/Hargata/user.suggested.improvement
Updated version number
2024-02-24 11:42:41 -07:00
DESKTOP-GENO133\IvanPlex
0dbaa68fc0 added copy and paste support for tags input 2024-02-24 11:41:16 -07:00
DESKTOP-GENO133\IvanPlex
80fd9e136a Updated version number 2024-02-24 11:10:28 -07:00
Hargata Softworks
ba27db5f75
Merge pull request #337 from hargata/Hargata/user.suggested.improvement
added help links
2024-02-24 10:26:41 -07:00
Hargata Softworks
4629271fb2
Update issue templates 2024-02-24 10:24:55 -07:00
DESKTOP-GENO133\IvanPlex
91177af98b added help links 2024-02-24 10:19:15 -07:00
Hargata Softworks
26281d1cbb
Merge pull request #336 from hargata/Hargata/user.suggested.improvement
added OIDC State validation
2024-02-24 09:53:12 -07:00
DESKTOP-GENO133\IvanPlex
a80c6e12ad added OIDC State validation 2024-02-24 06:14:44 -07:00
DESKTOP-GENO133\IvanPlex
8e208e8791 updated colors. 2024-02-22 22:43:51 -07:00
DESKTOP-GENO133\IvanPlex
6e87c2d5cc Updated website and re-organized readme 2024-02-22 22:36:56 -07:00
Hargata Softworks
3d96984735
Update README.md 2024-02-22 20:06:43 -07:00
Hargata Softworks
4626fdf04a
Merge pull request #330 from hargata/Hargata/minor.improvement
added state for OIDC
2024-02-22 16:04:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a2c013ec43 added state for OIDC 2024-02-22 15:59:35 -07:00
DESKTOP-GENO133\IvanPlex
9262131936 updated translation file. 2024-02-22 11:46:28 -07:00
Hargata Softworks
02c5302653
Update README.md 2024-02-22 11:20:19 -07:00
Hargata Softworks
fa73b61e3f
Merge pull request #328 from hargata/Hargata/generic.editor
added functions for pinning notes.
2024-02-22 10:48:22 -07:00
DESKTOP-GENO133\IvanPlex
96ade10289 added functions for pinning notes. 2024-02-22 10:46:18 -07:00
Hargata Softworks
acf16a6c28
Merge pull request #327 from hargata/Hargata/generic.editor
added clear overrides
2024-02-22 10:12:13 -07:00
DESKTOP-GENO133\IvanPlex
dedf8fc2f7 added method to replenish supplies. 2024-02-22 10:11:26 -07:00
DESKTOP-GENO133\IvanPlex
7099652083 added clear overrides 2024-02-22 08:56:31 -07:00
Hargata Softworks
d0f9795e63
Merge pull request #326 from hargata/Hargata/clean.up.usings
code cleanup.
2024-02-21 22:58:00 -07:00
DESKTOP-GENO133\IvanPlex
0ffc469ac8 fixed missing end span tag. fixed deprecated js function. 2024-02-21 22:57:16 -07:00
DESKTOP-GENO133\IvanPlex
b5ea3c63a1 code cleanup. 2024-02-21 22:48:37 -07:00
Hargata Softworks
5901531ddf
Merge pull request #325 from hargata/Hargata/bulk.selection
Hargata/bulk.selection
2024-02-21 22:33:19 -07:00
DESKTOP-GENO133\IvanPlex
814aa8d22b obsolete using 2024-02-21 22:30:25 -07:00
DESKTOP-GENO133\IvanPlex
d3aa55f987 Updated version number. 2024-02-21 22:29:47 -07:00
DESKTOP-GENO133\IvanPlex
8b7fb7ef0f added edit functionality for services, repairs, and upgrades 2024-02-21 22:28:25 -07:00
DESKTOP-GENO133\IvanPlex
0fab57d19c added bulk edit functionality. 2024-02-21 21:39:39 -07:00
DESKTOP-GENO133\IvanPlex
0d8822c496 reorganized folders. 2024-02-21 20:54:14 -07:00
Hargata Softworks
7235931f38
Merge pull request #322 from hargata/Hargata/bulk.selection
Bulk Operations.
2024-02-21 16:49:41 -07:00
DESKTOP-GENO133\IvanPlex
410f45c6c2 ssssss 2024-02-21 16:22:12 -07:00
DESKTOP-GENO133\IvanPlex
9a44d80be4 fixed compatibility on hybrid device. 2024-02-21 15:51:38 -07:00
DESKTOP-GENO133\IvanPlex
2f52ed64ba standardize experience across mobile devices 2024-02-21 15:05:19 -07:00
DESKTOP-GENO133\IvanPlex
dd68dec05c moved stuff around and added duplicate option. 2024-02-21 13:45:08 -07:00
DESKTOP-GENO133\IvanPlex
a8bc9f90e1 display number of records manipulated. 2024-02-21 12:57:09 -07:00
DESKTOP-GENO133\IvanPlex
8759216dd6 only select rows that are visible. 2024-02-21 12:50:11 -07:00
DESKTOP-GENO133\IvanPlex
0851e0a222 made things mobile friendly. 2024-02-21 12:38:50 -07:00
DESKTOP-GENO133\IvanPlex
40f1f1380e added select all to context menu for mobile friendliness. 2024-02-21 11:53:00 -07:00
DESKTOP-GENO133\IvanPlex
2a5ff7a911 added ability to select reminders from tax modal. 2024-02-21 10:32:25 -07:00
DESKTOP-GENO133\IvanPlex
0590f991d2 added shared method to push back recurring reminders. 2024-02-21 10:10:28 -07:00
DESKTOP-GENO133\IvanPlex
c6a96df3e9 updated readme 2024-02-20 21:21:20 -07:00
DESKTOP-GENO133\IvanPlex
0088c74b20 added ability to select rows when ctrl key is held. 2024-02-20 20:23:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6af0d8b88e added functionality to delete records in bulk. 2024-02-19 22:32:07 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5204a71b00 added option to bulk move records across service, upgrade, and repair. 2024-02-19 21:57:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8b2866b89b added ability to select bulk data. 2024-02-19 16:48:12 -07:00
Hargata Softworks
65f638f336
Merge pull request #319 from hargata/Hargata/csv.odo.insert
Hargata/csv.odo.insert
2024-02-19 14:00:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f92e95e01c Merge branch 'main' into Hargata/report.usability 2024-02-19 13:59:04 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2aeae70060 Enabled Auto Odometer CSV when importing via CSV. 2024-02-19 13:58:23 -07:00
Hargata Softworks
98b1cd9bc5
Merge pull request #315 from hargata/Hargata/linked.planner.to.reminder
check if reminder still exists.
2024-02-18 15:04:45 -07:00
DESKTOP-GENO133\IvanPlex
a02684f921 check if reminder still exists. 2024-02-18 15:04:02 -07:00
Hargata Softworks
13ddc6309e
Merge pull request #314 from hargata/Hargata/linked.planner.to.reminder
Updated version number
2024-02-18 14:42:42 -07:00
DESKTOP-GENO133\IvanPlex
2b2fd3ae28 Updated version number 2024-02-18 14:42:05 -07:00
Hargata Softworks
2dd3dc1718
Merge pull request #313 from hargata/Hargata/linked.planner.to.reminder
added ability to create plans from reminders.
2024-02-18 14:23:39 -07:00
DESKTOP-GENO133\IvanPlex
fe1b96d58c updated translation 2024-02-18 14:22:43 -07:00
DESKTOP-GENO133\IvanPlex
8dfee0fd12 added ability to create plans from reminders. 2024-02-18 14:20:20 -07:00
Hargata Softworks
275b60aa14
Merge pull request #310 from hargata/Hargata/supply.usage.history
Supply Usage History
2024-02-18 13:16:46 -07:00
DESKTOP-GENO133\IvanPlex
dd14b71e68 added front end to show used supplies. 2024-02-18 11:32:04 -07:00
DESKTOP-GENO133\IvanPlex
849171c4df moved function to shared js. 2024-02-17 21:43:19 -07:00
DESKTOP-GENO133\IvanPlex
2183c77606 added button to show supply requisition history. 2024-02-17 19:13:09 -07:00
Hargata Softworks
bedf7bad34
Merge pull request #306 from hargata/Hargata/report.usability
Improved Usability
2024-02-17 17:13:41 -07:00
DESKTOP-GENO133\IvanPlex
802c7923d5 added supply usage history 2024-02-17 17:07:31 -07:00
DESKTOP-GENO133\IvanPlex
95c8cd19f8 Updated error message 2024-02-17 10:53:29 -07:00
DESKTOP-GENO133\IvanPlex
e700a5f1c2 functionality to duplicate collaborators across vehicles. 2024-02-17 10:51:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7fd7f7e29d added select all option to reports page. 2024-02-16 15:50:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
902ddd0269 improve usability on reports dropdown 2024-02-16 15:29:06 -07:00
Hargata Softworks
0a6424f8c0
Merge pull request #305 from hargata/Hargata/user.provisioning
revert
2024-02-16 14:07:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3a36c624ba revert 2024-02-16 14:07:22 -07:00
Hargata Softworks
1329beb808
Merge pull request #304 from hargata/Hargata/user.provisioning
attempt to fix docker build.
2024-02-16 14:05:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0e8ef0180f attempt to fix docker build. 2024-02-16 14:05:01 -07:00
Hargata Softworks
660f089035
Merge pull request #303 from hargata/Hargata/user.provisioning
Hargata/user.provisioning
2024-02-16 13:57:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
04f90ae6a8 Added Distance Traveled Chart 2024-02-16 13:55:22 -07:00
DESKTOP-T0O5CDB\DESK-555BD
30b4a73fef made datepicker honor locale's week start day 2024-02-16 11:58:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7158f3bf0 automatically log open id user in after they've registered. 2024-02-16 11:06:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f46bbe9963 Allow OpenID Users to sign up with a token. 2024-02-16 10:48:20 -07:00
Hargata Softworks
9f73068a9e
Merge pull request #301 from hargata/Hargata/oidc.auth
Updated app version.
2024-02-15 20:12:23 -07:00
DESKTOP-GENO133\IvanPlex
779b18802b Updated app version. 2024-02-15 20:11:39 -07:00
Hargata Softworks
dce9acf47f
Merge pull request #300 from hargata/Hargata/oidc.auth
Hargata/OIDC.auth
2024-02-15 20:08:54 -07:00
DESKTOP-GENO133\IvanPlex
c9a925f548 Updated section name 2024-02-15 19:10:38 -07:00
DESKTOP-GENO133\IvanPlex
4eeec7887a Updated wording 2024-02-15 19:09:50 -07:00
Hargata Softworks
156c781bd7
Merge pull request #297 from hargata/Hargata/bugfix
Fixed ExtraFields upsert query.
2024-02-15 19:07:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
296cf92022 added error handling for users not registered with LubeLogger 2024-02-15 19:02:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d5f0e57c3b Added OpenID login. 2024-02-15 18:57:22 -07:00
DESKTOP-GENO133\IvanPlex
86ba6200fc Fixed ExtraFields upsert query. 2024-02-15 09:26:01 -07:00
Hargata Softworks
ac4ea07319
Merge pull request #291 from hargata/Hargata/further.improvements
styling changes.
2024-02-14 12:22:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6d380de603 styling changes. 2024-02-14 12:21:39 -07:00
Hargata Softworks
e789bc6925
Merge pull request #290 from hargata/Hargata/further.improvements
server timezone offset.
2024-02-14 12:15:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f7481be91c Updated version. 2024-02-14 12:14:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a8151bcf0e server timezone offset. 2024-02-14 12:12:49 -07:00
Hargata Softworks
0b6a6787bb
Merge pull request #289 from hargata/Hargata/further.improvements
Further Improvements
2024-02-14 11:02:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7302982366 removed empty script tag. 2024-02-14 11:01:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
19b7dfc90a added calendar view modal 2024-02-14 10:56:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
29825a6ad6 Merge branch 'main' into Hargata/further.improvements
# Conflicts:
#	wwwroot/js/garage.js
2024-02-14 09:21:16 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f6493f0496 Further improvements to calendar page. 2024-02-14 09:18:47 -07:00
Hargata Softworks
c2d61eecc0
Merge pull request #285 from hargata/Hargata/encode.html
Encode HTML Inputs.
2024-02-13 17:46:13 -07:00
DESKTOP-GENO133\IvanPlex
2a4354c52e Encode HTML Inputs. 2024-02-13 17:45:41 -07:00
Hargata Softworks
e7dc4f67f5
Merge pull request #284 from hargata/Hargata/calendar.view
Lock down contoller methods for extra fields.
2024-02-13 16:48:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8584d2cf9c Lock down contoller methods for extra fields. 2024-02-13 16:48:24 -07:00
Hargata Softworks
92b9c6953c
Merge pull request #283 from hargata/Hargata/calendar.view
fixed bug for records with deleted views.
2024-02-13 16:17:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ac1c2ca7fe fixed bug for records with deleted views. 2024-02-13 16:16:49 -07:00
Hargata Softworks
f05ca7f1f8
Merge pull request #282 from hargata/Hargata/calendar.view
Hargata/calendar.view
2024-02-13 16:03:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ecef1c8640 cleaner code. 2024-02-13 16:02:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
43d58a4b7e Fixed bug with styling. 2024-02-13 15:49:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8293b376a2 Added a Calendar View 2024-02-13 15:36:25 -07:00
Hargata Softworks
c243cdd54c
Merge pull request #276 from hargata/Hargata/extra.fields
Extra Fields
2024-02-12 20:59:53 -07:00
Hargata Softworks
0383257356
Merge pull request #278 from hargata/Hargata/numeric.odo
Numeric keyboard for odometer field.
2024-02-12 20:56:33 -07:00
DESKTOP-GENO133\IvanPlex
73e8731bd4 Numeric keyboard for odometer field. 2024-02-12 20:56:03 -07:00
DESKTOP-GENO133\IvanPlex
b0e82889e0 updated translation, fixed postgres method for extra field insert. 2024-02-12 20:28:36 -07:00
Hargata Softworks
8e28116371
Merge pull request #277 from hargata/Hargata/documentation.static
Static File Documentation
2024-02-12 19:29:06 -07:00
DESKTOP-GENO133\IvanPlex
ed717af91c further optimization. 2024-02-12 19:26:11 -07:00
DESKTOP-GENO133\IvanPlex
7ae5a982e9 updated this thang. 2024-02-12 18:31:11 -07:00
DESKTOP-GENO133\IvanPlex
2e3f1e6763 updated translation file. 2024-02-12 18:30:44 -07:00
DESKTOP-GENO133\IvanPlex
4a2e02afc8 fixed bug where required fields don't persist. 2024-02-12 18:30:03 -07:00
DESKTOP-GENO133\IvanPlex
9c53850faa added front end for displaying extra fields. 2024-02-12 17:39:09 -07:00
DESKTOP-GENO133\IvanPlex
695b15faed added UI to add/edit/delete extra fields. 2024-02-12 17:36:13 -07:00
DESKTOP-GENO133\IvanPlex
33aeaf9825 front end logic for saving extra fields. 2024-02-12 17:32:08 -07:00
DESKTOP-GENO133\IvanPlex
dc3608524a fomatting. 2024-02-12 15:04:32 -07:00
DESKTOP-GENO133\IvanPlex
6996814888 added backend for storing extra fields. 2024-02-12 15:04:02 -07:00
DESKTOP-GENO133\IvanPlex
9bbaf49ade Static documentation cloned from otterwiki 2024-02-12 13:59:53 -07:00
DESKTOP-GENO133\IvanPlex
db737df712 removed empty extra fields from being added to object. 2024-02-12 09:15:05 -07:00
DESKTOP-GENO133\IvanPlex
bae1839c06 added JS to retrieve and validate extra fields. 2024-02-12 09:05:59 -07:00
DESKTOP-GENO133\IvanPlex
7e70f5108f changed dictionary to class object so we can enforce field requirement. 2024-02-12 03:27:26 -07:00
DESKTOP-GENO133\IvanPlex
c5c0a6900b added extra fields attribute to object classes. 2024-02-11 19:08:27 -07:00
Hargata Softworks
50b88dd7a1
Merge pull request #275 from hargata/Hargata/supplies.improvement
updated version number and fixed labels not updating in supplies page
2024-02-11 18:36:33 -07:00
DESKTOP-GENO133\IvanPlex
e44eb991f5 updated version number and fixed labels not updating in supplies page 2024-02-11 18:14:34 -07:00
Hargata Softworks
39d0d346ad
Merge pull request #274 from hargata/Hargata/supplies.improvement
fixed supply requisition when used with european units.
2024-02-11 14:41:31 -07:00
DESKTOP-GENO133\IvanPlex
142865f1ce fixed supply requisition when used with european units. 2024-02-11 14:40:47 -07:00
Hargata Softworks
2c18755a0f
Merge pull request #273 from hargata/Hargata/supplies.improvement
Supplies Improvement
2024-02-11 13:08:08 -07:00
DESKTOP-GENO133\IvanPlex
d3be77bf09 fixed div name, 2024-02-11 12:47:43 -07:00
DESKTOP-GENO133\IvanPlex
bcafd9e97e added tags in supplies requisition modal. 2024-02-11 12:46:34 -07:00
DESKTOP-GENO133\IvanPlex
f581e55842 added tags to general supply record tab. 2024-02-11 09:48:54 -07:00
DESKTOP-GENO133\IvanPlex
dfe4e4f1ab added check for missing supplies 2024-02-11 09:24:48 -07:00
Hargata Softworks
080e2a1667
Merge pull request #269 from hargata/Hargata/num.input
added attributes to prompt numeric inputs without actually limiting t…
2024-02-10 22:53:57 -07:00
DESKTOP-GENO133\IvanPlex
01cbee00fd added attributes to prompt numeric inputs without actually limiting the user to numbers. 2024-02-10 22:52:22 -07:00
Hargata Softworks
f0869c741f
Merge pull request #266 from hargata/Hargata/postgres.fix.fix
added enable shop supplies to config helper
2024-02-10 15:15:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6dc690011d added enable shop supplies to config helper 2024-02-10 14:32:11 -07:00
Hargata Softworks
65891c7c11
Merge pull request #265 from hargata/Hargata/postgres.fix.fix
additional fixes
2024-02-10 14:14:25 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d306152d38 additional fixes 2024-02-10 14:11:31 -07:00
DESKTOP-GENO133\IvanPlex
b372f64e11 ignored more files. 2024-02-10 13:01:38 -07:00
DESKTOP-GENO133\IvanPlex
baff93bd96 updated gitignore 2024-02-10 12:57:58 -07:00
DESKTOP-GENO133\IvanPlex
abbe13b269 stop tracking userConfig.json 2024-02-10 12:57:38 -07:00
Hargata Softworks
fb43932e0d
Merge pull request #264 from hargata/Hargata/postgres.fix
Updated command.
2024-02-10 11:47:15 -07:00
DESKTOP-GENO133\IvanPlex
c10aa73e5e Updated command. 2024-02-10 11:46:34 -07:00
Hargata Softworks
be09f3e9dd
Merge pull request #263 from hargata/Hargata/postgres.fix
only open connection if it's closed.
2024-02-10 11:20:01 -07:00
DESKTOP-GENO133\IvanPlex
d3f94337ae only open connection if it's closed. 2024-02-10 11:19:27 -07:00
Hargata Softworks
46a227453e
Merge pull request #262 from hargata/Hargata/custom.month.interval
added ability to add custom month interval to reminder.
2024-02-10 10:49:33 -07:00
DESKTOP-GENO133\IvanPlex
8981d69844 added custom month interval for taxes/fees 2024-02-10 10:47:33 -07:00
DESKTOP-GENO133\IvanPlex
09fbc37472 added ability to add custom month interval to reminder. 2024-02-10 09:33:30 -07:00
Hargata Softworks
5cee1ea7cf
Merge pull request #261 from hargata/Hargata/taginput.tweaks
change make backup to create backup.
2024-02-10 08:48:09 -07:00
DESKTOP-GENO133\IvanPlex
cd9a673e62 change make backup to create backup. 2024-02-10 08:47:08 -07:00
Hargata Softworks
dd9f754e85
Merge pull request #254 from hargata/Hargata/taginput.tweaks
Minor tweaks and QOL improvement
2024-02-09 22:26:52 -07:00
DESKTOP-GENO133\IvanPlex
7d22d1ddc4 auto create config directory if it doesn't exist. 2024-02-09 22:23:18 -07:00
DESKTOP-GENO133\IvanPlex
d5f562b48f removed obsolete typeahead script and fixed input spacing on mobile devices. 2024-02-09 21:57:01 -07:00
DESKTOP-GENO133\IvanPlex
bb64ee4012 Fixed to return redirect to unauthorized page instead of hardcoded view name 2024-02-09 15:09:11 -07:00
DESKTOP-GENO133\IvanPlex
a60430b4b2 Fixed 401 redirect 2024-02-09 15:07:43 -07:00
Hargata Softworks
117a172bc2
Merge pull request #253 from hargata/Hargata/postgres
Added Postgres backend support.
2024-02-09 14:56:08 -07:00
DESKTOP-GENO133\IvanPlex
b93b7f321e fixed export file path. 2024-02-09 14:55:29 -07:00
DESKTOP-GENO133\IvanPlex
f34f3da587 added export functionality. 2024-02-09 14:32:30 -07:00
DESKTOP-GENO133\IvanPlex
8104f852d8 added partial export functionality 2024-02-09 14:05:26 -07:00
DESKTOP-GENO133\IvanPlex
32e58e6280 added import function for migration tool. 2024-02-09 13:34:10 -07:00
DESKTOP-GENO133\IvanPlex
c4dc81e4bc fixed user access issue. 2024-02-09 10:34:01 -07:00
DESKTOP-GENO133\IvanPlex
9fe0396abe added token data access for pg 2024-02-09 09:55:02 -07:00
DESKTOP-GENO133\IvanPlex
ad2b58697a added post gres backend for user record. 2024-02-09 09:46:47 -07:00
DESKTOP-GENO133\IvanPlex
4845b02fc8 updated to return null instead of empty object. 2024-02-08 20:18:17 -07:00
DESKTOP-GENO133\IvanPlex
1ed53d5e4b added pg data access for user config record. 2024-02-08 19:50:50 -07:00
DESKTOP-GENO133\IvanPlex
00622126d7 added more generic data access classes for postgres. 2024-02-08 19:26:42 -07:00
DESKTOP-GENO133\IvanPlex
b5cdd66248 added tax and reminder pg data access 2024-02-08 18:15:33 -07:00
DESKTOP-GENO133\IvanPlex
78408427b8 only init endpoint list if vehicleId is 0. 2024-02-08 17:54:42 -07:00
DESKTOP-GENO133\IvanPlex
baf9e8e833 Merge branch 'main' into Hargata/postgres 2024-02-08 17:50:30 -07:00
DESKTOP-GENO133\IvanPlex
bde00a26d5 added pg data access for repair records. 2024-02-08 17:50:06 -07:00
Hargata Softworks
6be6a2196e
Merge pull request #251 from hargata/Hargata/shop.supplies
checked endpoints.
2024-02-08 17:41:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b8dab3d4a4 checked endpoints. 2024-02-08 17:41:00 -07:00
Hargata Softworks
447c929386
Merge pull request #250 from hargata/Hargata/shop.supplies
fixed collaboratorfilter so that user can only access shop supplies
2024-02-08 17:33:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9e37c01a83 fixed collaboratorfilter so that user can only access shop supplies 2024-02-08 17:32:06 -07:00
Hargata Softworks
3d39dc8ed8
Merge pull request #249 from hargata/Hargata/shop.supplies
consolidated settings into confighelper, fixed shop supplies access i…
2024-02-08 16:55:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08ace8b08d consolidated settings into confighelper, fixed shop supplies access issue for non root user. 2024-02-08 16:54:01 -07:00
Hargata Softworks
cbf95b1e04
Merge pull request #248 from hargata/Hargata/shop.supplies
hide shop supplies by default unless enabled by root user.
2024-02-08 16:20:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fd8f93ee5f hide shop supplies by default unless enabled by root user. 2024-02-08 16:18:29 -07:00
Hargata Softworks
4f146f9427
Merge pull request #247 from hargata/Hargata/shop.supplies
added garage level supplies
2024-02-08 15:36:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
19ace06c08 fixed markdown not working. 2024-02-08 15:34:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4bfe947ce1 added garage level supplies 2024-02-08 15:30:24 -07:00
DESKTOP-GENO133\IvanPlex
4850b38e7f updated version and dependencies. 2024-02-08 08:57:17 -07:00
DESKTOP-GENO133\IvanPlex
e5f19c5f20 added injection for gas record pg data access 2024-02-08 08:48:33 -07:00
DESKTOP-GENO133\IvanPlex
8c4212678f added gas record pg data access 2024-02-08 08:47:50 -07:00
DESKTOP-GENO133\IvanPlex
573e80728c Merge branch 'main' into Hargata/postgres 2024-02-08 08:38:19 -07:00
DESKTOP-GENO133\IvanPlex
c88fa690f6 added postgres data access for service records. 2024-02-08 08:33:23 -07:00
DESKTOP-GENO133\IvanPlex
1feb89acf5 simplified vehicle data access. 2024-02-07 21:08:11 -07:00
DESKTOP-GENO133\IvanPlex
8be252e949 added note data access for postgres. 2024-02-07 21:02:54 -07:00
DESKTOP-GENO133\IvanPlex
3aaf230a60 made add vehicle method more efficient. 2024-02-07 20:33:51 -07:00
Hargata Softworks
27126638be
Merge pull request #244 from hargata/Hargata/deltamileage
swap min max label on filter.
2024-02-07 13:12:38 -07:00
DESKTOP-GENO133\IvanPlex
8c3a001930 swap min max label on filter. 2024-02-07 13:11:18 -07:00
Hargata Softworks
2b2f181888
Merge pull request #243 from hargata/Hargata/deltamileage
fixed max and min labels not updating.
2024-02-07 12:49:04 -07:00
DESKTOP-GENO133\IvanPlex
66557fa126 fixed max and min labels not updating. 2024-02-07 12:48:34 -07:00
Hargata Softworks
7c00951d74
Merge pull request #242 from hargata/Hargata/deltamileage
Hargata/deltamileage
2024-02-07 12:32:36 -07:00
DESKTOP-GENO133\IvanPlex
1087ed56ce added feature to auto add to odometer when user adds via API. 2024-02-07 10:51:04 -07:00
DESKTOP-GENO133\IvanPlex
dd2cfd90b1 added method to toggle delta mileage. 2024-02-07 10:41:58 -07:00
DESKTOP-GENO133\IvanPlex
e45339f75c fixed vehicle id = 0 2024-02-07 09:19:36 -07:00
DESKTOP-GENO133\IvanPlex
9c225ff7a0 postgres backend for vehicle. 2024-02-06 20:24:05 -07:00
DESKTOP-GENO133\IvanPlex
6cdfba420f moved existing external implementations into litedb folder 2024-02-06 19:10:28 -07:00
Hargata Softworks
cc43d45c9c
Merge pull request #234 from hargata/Hargata/email.reminder.template
created email template for reminder emails.
2024-02-06 16:13:04 -07:00
Hargata Softworks
dba51f171d
Merge pull request #237 from hargata/Hargata/plan.template
Planner Templates
2024-02-06 16:07:26 -07:00
DESKTOP-GENO133\IvanPlex
0bbf9f783f added more translation keys. 2024-02-06 16:04:34 -07:00
DESKTOP-GENO133\IvanPlex
a52ac6a41b Front end for creating plan records from templates. 2024-02-06 15:54:32 -07:00
DESKTOP-GENO133\IvanPlex
a542c91dc3 added backend for saving and retrieving plan record templates. 2024-02-06 12:45:39 -07:00
DESKTOP-GENO133\IvanPlex
6bcbb0cc86 created email template for reminder emails. 2024-02-06 06:12:13 -07:00
DESKTOP-GENO133\IvanPlex
7a78d51c79 Update version number. 2024-02-05 20:30:13 -07:00
Hargata Softworks
3c2715840e
Merge pull request #233 from hargata/Hargata/increase.upload.size
1.1.3
2024-02-05 18:29:36 -07:00
DESKTOP-GENO133\IvanPlex
dfbd90aaf4 Merge branch 'main' into Hargata/increase.upload.size
# Conflicts:
#	Views/Login/Index.cshtml
2024-02-05 18:26:14 -07:00
DESKTOP-GENO133\IvanPlex
2b3a4ce8ce more things to translate 2024-02-05 18:23:06 -07:00
DESKTOP-GENO133\IvanPlex
135292f2b4 added attachment features to Notes. 2024-02-05 18:08:54 -07:00
Hargata Softworks
499c697b0b
Merge pull request #229 from hargata/Hargata/redirectURL
added redirectURL
2024-02-05 08:46:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fb74f01609 added redirectURL 2024-02-05 08:44:49 -07:00
DESKTOP-GENO133\IvanPlex
6ab2216742 greatly increase the max upload size. 2024-02-04 15:32:34 -07:00
Hargata Softworks
7c97aa70a0
Merge pull request #228 from hargata/Hargata/translation.helper
added try catch to translation layer for translation files error.
2024-02-04 12:56:51 -07:00
DESKTOP-GENO133\IvanPlex
ff255e8293 added try catch to translation layer for translation files error. 2024-02-04 12:56:24 -07:00
Hargata Softworks
57dc976a9f
Merge pull request #227 from jdh313/docker-image-tags
ci: generate versioned tags
2024-02-04 11:46:27 -07:00
Jacob Hoehler
13361241e8 ci: generate versioned tags
Generates an image tag based on release tag, as well as generating a `latest` tag. Also generates an `edge` tag for the latest pushes to the main branch.
2024-02-04 13:17:45 -05:00
Hargata Softworks
332af9a04a
Merge pull request #224 from hargata/Hargata/translation.helper
UI Translation
2024-02-04 10:55:09 -07:00
DESKTOP-GENO133\IvanPlex
961c60f936 fixed menu styling. 2024-02-04 10:53:23 -07:00
DESKTOP-GENO133\IvanPlex
34483886be added translation volume so that it can persist. 2024-02-04 10:49:22 -07:00
DESKTOP-GENO133\IvanPlex
548989f9ae keyed admin panel 2024-02-04 10:09:57 -07:00
DESKTOP-GENO133\IvanPlex
83a59aff37 set different path for default language. 2024-02-03 22:49:18 -07:00
DESKTOP-GENO133\IvanPlex
60f28b16e7 Merge remote-tracking branch 'origin/main' into Hargata/translation.helper 2024-02-03 22:30:52 -07:00
DESKTOP-GENO133\IvanPlex
9cb8bbd5b7 added default language settings. 2024-02-03 22:15:53 -07:00
DESKTOP-GENO133\IvanPlex
f7c95e9b36 more forgotten ones. 2024-02-03 22:11:47 -07:00
DESKTOP-GENO133\IvanPlex
cd37eedd15 updated default translation file. 2024-02-03 22:10:42 -07:00
DESKTOP-GENO133\IvanPlex
7d11c4003f keyed the rest. 2024-02-03 21:58:17 -07:00
DESKTOP-GENO133\IvanPlex
7d9b67a04d keyed taxes 2024-02-03 20:51:18 -07:00
DESKTOP-GENO133\IvanPlex
aba178a324 fix html encodings. 2024-02-03 20:36:32 -07:00
Hargata Softworks
4ecdaf5225
Merge pull request #225 from hargata/Hargata/unified.error.message
Fixed Odometer Validation
2024-02-03 20:16:39 -07:00
DESKTOP-GENO133\IvanPlex
c0bc9f7a14 fix odometer validation. 2024-02-03 20:13:59 -07:00
DESKTOP-GENO133\IvanPlex
094d96d880 unified error messages. 2024-02-03 20:07:05 -07:00
DESKTOP-GENO133\IvanPlex
a8fad6f160 keyed supplies 2024-02-03 18:46:21 -07:00
DESKTOP-GENO133\IvanPlex
6befd5ca76 keyed service record. 2024-02-03 17:51:26 -07:00
DESKTOP-GENO133\IvanPlex
046812ff27 keyed reminders and report page. 2024-02-03 16:51:07 -07:00
DESKTOP-GENO133\IvanPlex
db1778a749 keyed plans 2024-02-03 15:38:57 -07:00
DESKTOP-GENO133\IvanPlex
f27fa1625c missed a few tags. 2024-02-03 15:13:59 -07:00
DESKTOP-GENO133\IvanPlex
39a9d5f95b keyed up to odometer. 2024-02-03 14:41:33 -07:00
DESKTOP-GENO133\IvanPlex
234d7163fc added caching to reduce number of IO reads. 2024-02-03 11:01:54 -07:00
DESKTOP-GENO133\IvanPlex
6f64cc9996 keyed labels. 2024-02-03 10:10:15 -07:00
DESKTOP-GENO133\IvanPlex
58e3b49bdf updated en_US translation. 2024-02-03 10:04:47 -07:00
DESKTOP-GENO133\IvanPlex
ca2dc1e1e0 keyed gas modal. 2024-02-03 10:03:38 -07:00
DESKTOP-GENO133\IvanPlex
c65dca4c58 removed hardcoded string from gasrecord js. 2024-02-03 09:57:28 -07:00
DESKTOP-GENO133\IvanPlex
82c2351b00 keyed gas tab. 2024-02-03 09:51:42 -07:00
DESKTOP-GENO133\IvanPlex
f31ca163e5 Merge branch 'Hargata/tags.export' into Hargata/translation.helper 2024-02-03 06:28:32 -07:00
Hargata Softworks
351db5de95
Merge pull request #222 from hargata/Hargata/tags.export
added tags export to csv.
2024-02-03 06:13:06 -07:00
DESKTOP-GENO133\IvanPlex
41164516f7 added tags export to csv. 2024-02-03 06:11:59 -07:00
DESKTOP-GENO133\IvanPlex
29a3c815fc added translation helper. 2024-02-02 21:29:44 -07:00
Hargata Softworks
1768063e57
Merge pull request #218 from hargata/Hargata/update.count
Update count and aggregates after filtering by tags.
2024-02-02 17:05:02 -07:00
Hargata Softworks
35ce5603e5
Merge pull request #219 from hargata/Hargata/gas.tags
Hargata/gas.tags
2024-02-02 17:04:28 -07:00
DESKTOP-GENO133\IvanPlex
19e38f9885 fixed gas odometer reading validation. 2024-02-02 17:03:56 -07:00
DESKTOP-GENO133\IvanPlex
c55528b8a0 Re-calculate gas tag aggregate labels. 2024-02-02 16:58:37 -07:00
DESKTOP-GENO133\IvanPlex
1c00f31312 added tags functionality to gas tab. 2024-02-02 15:33:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8e66530544 Update count and aggregates after filtering by tags. 2024-02-02 12:15:34 -07:00
Hargata Softworks
7bdd3d902b
Merge pull request #217 from hargata/Hargata/tags.other.tab
add tagging functionality to notes, odometer, and tax tabs
2024-02-02 09:08:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
abb44608fe add tagging functionality to notes, odometer, and tax tabs 2024-02-02 09:06:11 -07:00
Hargata Softworks
0b1e3f4be8
Merge pull request #216 from tylerbrewer2/main
merge GHA build and push workflows
2024-02-02 08:11:19 -07:00
tylerbrewer2
156eb8ec27 merge GHA build and push workflows 2024-02-02 10:07:13 -05:00
Hargata Softworks
472dcac590
Merge pull request #215 from hargata/Hargata/sort.gas.tab
Hargata/sort.gas.tab
2024-02-02 07:09:03 -07:00
DESKTOP-GENO133\IvanPlex
b4d86f415c only sort if garage is active tab 2024-02-02 07:07:04 -07:00
DESKTOP-GENO133\IvanPlex
812e768f93 added sorting functionality to touch screen devices. 2024-02-02 07:01:42 -07:00
Hargata Softworks
a08d4798e1
Merge pull request #214 from hargata/Hargata/sort.gas.tab
Hargata/sort.gas.tab
2024-02-01 20:34:32 -07:00
DESKTOP-GENO133\IvanPlex
7bc08c9950 added stripe link. 2024-02-01 20:33:44 -07:00
DESKTOP-GENO133\IvanPlex
20107e3f05 Merge branch 'main' into Hargata/sort.gas.tab 2024-02-01 20:22:16 -07:00
DESKTOP-GENO133\IvanPlex
ed678c36a5 add sorting to garage tab. 2024-02-01 20:21:49 -07:00
Hargata Softworks
f7574a9a60
Merge pull request #212 from hargata/Hargata/tags.autocomplete
added datalist to tags field.
2024-02-01 16:59:56 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e506b543e6 added datalist to tags field. 2024-02-01 13:50:30 -07:00
Hargata Softworks
019a549b64
Merge pull request #211 from hargata/Hargata/fixed.gas.fr
fixed gas settings for real
2024-02-01 10:16:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8dbc883a8c fixed gas settings for real 2024-02-01 10:14:46 -07:00
Hargata Softworks
5fd918115c
Merge pull request #210 from hargata/Hargata/gas.settings.fix
prevent the settings page from overriding the fuel tab settings.
2024-02-01 09:34:05 -07:00
DESKTOP-GENO133\IvanPlex
a11f8377fe prevent the settings page from overriding the fuel tab settings. 2024-02-01 09:32:47 -07:00
Hargata Softworks
7be19f8603
Merge pull request #209 from hargata/Hargata/utf.bug.dashboard
Fix UTF encoding bug in dashboard.
2024-02-01 07:00:41 -07:00
DESKTOP-GENO133\IvanPlex
5407ebecb1 Fix UTF encoding bug in dashboard. 2024-02-01 06:59:54 -07:00
Hargata Softworks
b75a1b4e6c
Update README.md 2024-01-31 17:56:17 -07:00
Hargata Softworks
1c28039aaf
Merge pull request #207 from hargata/Hargata/persist.fuel.settings
fix user preferrred unit in vehicle consolidated report.
2024-01-31 17:44:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3a1836ef84 fix user preferrred unit in vehicle consolidated report. 2024-01-31 17:43:38 -07:00
Hargata Softworks
c747889f85
Merge pull request #206 from hargata/Hargata/persist.fuel.settings
persist gas tab settings.
2024-01-31 17:28:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
338a8426b2 persist gas tab settings. 2024-01-31 17:14:26 -07:00
Hargata Softworks
4b1701d158
Merge pull request #204 from hargata/Hargata/british.fuelunit.toggle
take into account unicode encoding.
2024-01-31 15:13:59 -07:00
DESKTOP-GENO133\IvanPlex
5d64a9a422 take into account unicode encoding. 2024-01-31 15:13:14 -07:00
Hargata Softworks
c2315db127
Merge pull request #203 from hargata/Hargata/british.fuelunit.toggle
Added alternate fuel units.
2024-01-31 14:30:52 -07:00
DESKTOP-GENO133\IvanPlex
e40129f701 Added alternate fuel units. 2024-01-31 14:29:35 -07:00
Hargata Softworks
d198ad9a6b
Merge pull request #202 from hargata/Hargata/deltamileage.zero.fix
prevent delta mileage from being 0.
2024-01-31 11:39:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
abffc5ab60 prevent delta mileage from being 0. 2024-01-31 11:39:14 -07:00
Hargata Softworks
24ff9db32d
Merge pull request #200 from hargata/Hargata/gas.tweaks
adjust for missed fuel ups.
2024-01-31 11:05:57 -07:00
DESKTOP-GENO133\IvanPlex
07fa560344 adjust for missed fuel ups. 2024-01-31 11:02:40 -07:00
Hargata Softworks
28615d9038
Merge pull request #198 from hargata/Hargata/tags
Hargata/tags
2024-01-31 09:14:55 -07:00
DESKTOP-GENO133\IvanPlex
d8f53e4b65 removed comment, 2024-01-31 09:13:13 -07:00
DESKTOP-GENO133\IvanPlex
1f0cef4161 Merge branch 'Hargata/taghelper.fix' into Hargata/tags
# Conflicts:
#	Views/Shared/_Layout.cshtml
2024-01-31 09:07:20 -07:00
Hargata Softworks
af5d72e0d0
Merge pull request #197 from hargata/Hargata/taghelper.fix
removed taghelper methods.
2024-01-31 09:03:57 -07:00
DESKTOP-GENO133\IvanPlex
1397e92709 removed taghelper methods. 2024-01-31 09:03:10 -07:00
DESKTOP-GENO133\IvanPlex
a49121b1d9 fixed tagsinput and tooltip js error. 2024-01-31 08:44:59 -07:00
DESKTOP-GENO133\IvanPlex
ca65a6fb71 added spacebar as an action key. 2024-01-31 08:22:01 -07:00
DESKTOP-GENO133\IvanPlex
a54857c6bf added vehicle tags. 2024-01-31 08:10:49 -07:00
DESKTOP-GENO133\IvanPlex
2eadd05289 added tagging functionality to upgrades and repairs. 2024-01-30 22:44:25 -07:00
DESKTOP-GENO133\IvanPlex
6eafa5e036 fixed hiding styles. 2024-01-30 21:26:21 -07:00
DESKTOP-GENO133\IvanPlex
40f01ad100 tags filter. 2024-01-30 20:23:08 -07:00
DESKTOP-GENO133\IvanPlex
161b36325e modified tagsinput to use modern bootstrap styles, added tagging functionality to service record. 2024-01-30 18:59:35 -07:00
DESKTOP-GENO133\IvanPlex
f9a6ddd513 add tags 2024-01-30 18:06:39 -07:00
Hargata Softworks
e85110f8b6
Merge pull request #193 from hargata/Hargata/custom.mileage.interval
Custom Mileage Interval for Reminders
2024-01-30 17:19:39 -07:00
DESKTOP-GENO133\IvanPlex
76d3dba574 fix label display 2024-01-30 17:18:20 -07:00
DESKTOP-GENO133\IvanPlex
178b50a033 added ability for user to define custom mileage intervals. 2024-01-30 17:15:21 -07:00
Hargata Softworks
fb45aefde3
Merge pull request #192 from hargata/Hargata/alternate.logo
replace logo in login pages.
2024-01-30 15:32:57 -07:00
DESKTOP-GENO133\IvanPlex
05bbc8a95b replace logo in login pages. 2024-01-30 15:32:09 -07:00
Hargata Softworks
f123299dd6
Merge pull request #191 from hargata/Hargata/alternate.logo
prevent pinned note from being loaded over and over again.
2024-01-30 14:54:02 -07:00
DESKTOP-GENO133\IvanPlex
7174413e2a prevent pinned note from being loaded over and over again. 2024-01-30 14:53:10 -07:00
Hargata Softworks
d816f73598
Merge pull request #190 from hargata/Hargata/alternate.logo
allows users to inject their own logo.
2024-01-30 14:43:51 -07:00
DESKTOP-GENO133\IvanPlex
b249ccdd6c allows users to inject their own logo. 2024-01-30 14:42:54 -07:00
Hargata Softworks
4a007530a6
Merge pull request #188 from hargata/Hargata/parse.url
added setting to automatically load parsed markdown.
2024-01-30 08:19:04 -07:00
DESKTOP-GENO133\IvanPlex
04ce448b23 removed onclick event since it ain;t helping. 2024-01-30 08:18:45 -07:00
DESKTOP-GENO133\IvanPlex
ccd446f299 added setting to automatically load parsed markdown. 2024-01-30 08:16:28 -07:00
Hargata Softworks
c49c8a5301
Merge pull request #186 from hargata/Hargata/parse.url
Hargata/parse.url
2024-01-29 21:05:24 -07:00
DESKTOP-GENO133\IvanPlex
55cc2819d0 show overlay in place instead. 2024-01-29 21:04:22 -07:00
DESKTOP-GENO133\IvanPlex
f8de7de0d6 added markdown for named notes. 2024-01-29 20:33:37 -07:00
Hargata Softworks
2ea1bc2c20
Merge pull request #185 from hargata/Hargata/parse.url
Added Markdown Parsing.
2024-01-29 19:53:38 -07:00
DESKTOP-GENO133\IvanPlex
90d095ea51 Updated version. 2024-01-29 19:50:01 -07:00
DESKTOP-GENO133\IvanPlex
e1d12d0918 added dependency 2024-01-29 19:47:45 -07:00
DESKTOP-GENO133\IvanPlex
d9d0957040 added method to parse markdown data. 2024-01-29 19:43:53 -07:00
Hargata Softworks
9d73db3c51
Merge pull request #184 from hargata/Hargata/reduce.login.error
added env variable to reduce auth-related logs.
2024-01-29 17:21:07 -07:00
DESKTOP-GENO133\IvanPlex
c0f0786fd4 added env variable to reduce auth-related logs. 2024-01-29 17:20:39 -07:00
Hargata Softworks
357eff116f
Merge pull request #183 from hargata/Hargata/export.attachments
Export attachments for vehicle.
2024-01-29 13:33:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
32a047c522 added print function. 2024-01-29 13:32:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8a237bb7ec Export attachments for vehicle. 2024-01-29 13:24:30 -07:00
DESKTOP-GENO133\IvanPlex
f5b9072cc6 Updated Github pages. 2024-01-28 15:06:06 -07:00
Hargata Softworks
4e3eaa53ff
Merge pull request #180 from hargata/Hargata/hr-mpg
Hargata/hr mpg
2024-01-28 07:39:42 -07:00
Hargata Softworks
4779d3f161
Merge pull request #181 from hargata/Hargata/move.records
Move records around.
2024-01-28 07:38:04 -07:00
DESKTOP-GENO133\IvanPlex
b0173fae94 added object inheritance. 2024-01-28 07:37:12 -07:00
DESKTOP-GENO133\IvanPlex
c6ee8830a3 added hours per gallon calculation. 2024-01-27 23:29:05 -07:00
DESKTOP-GENO133\IvanPlex
c2eeab5025 Merge commit '43fd40347f34bc7676bfd5531abf612e06285e5c' into Hargata/hr-mpg 2024-01-27 22:53:55 -07:00
DESKTOP-GENO133\IvanPlex
43fd40347f added engine hours variable. 2024-01-27 21:14:29 -07:00
Hargata Softworks
53139f9bb2
Merge pull request #179 from hargata/Hargata/sorting.supplies
added sorting in supplies tab.
2024-01-27 12:17:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0b6033cc00 added sorting in supplies tab. 2024-01-27 12:16:49 -07:00
Hargata Softworks
6f0115e5c5
Merge pull request #176 from hargata/Hargata/pinned.notes.garage
touch screen support for pinned notes.
2024-01-27 07:41:06 -07:00
Hargata Softworks
2403adf537
Merge pull request #177 from hargata/Hargata/sort.columns
make cost columns sortable.
2024-01-27 07:40:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f00ab897b5 make cost columns sortable. 2024-01-27 07:37:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
093631bf6f touch screen support for pinned notes. 2024-01-26 13:19:37 -07:00
Hargata Softworks
5c2835ab76
Merge pull request #175 from hargata/Hargata/pinned.notes.garage
added maxfilesize label and error handling.
2024-01-26 12:42:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
03029981fd added maxfilesize label and error handling. 2024-01-26 12:40:39 -07:00
Hargata Softworks
69b1838038
Merge pull request #174 from hargata/Hargata/pinned.notes.garage
display pinned notes in garage.
2024-01-26 12:02:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c2f83026c display pinned notes in garage. 2024-01-26 12:01:53 -07:00
Hargata Softworks
d36f2c59e3
Merge pull request #172 from hargata/Hargata/demo.stuff
clear temp files restoring demo instance.
2024-01-26 09:26:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
53ebec3f03 clear temp files restoring demo instance. 2024-01-26 09:25:44 -07:00
Hargata Softworks
f2cbbaeb12
Merge pull request #170 from hargata/Hargata/average.fuel.fix
include partial fuel up fuel consumption in average mpg.
2024-01-25 22:19:02 -07:00
DESKTOP-GENO133\IvanPlex
31c1202649 include partial fuel up fuel consumption in average mpg. 2024-01-25 22:18:33 -07:00
Hargata Softworks
331499461a
Merge pull request #148 from hargata/Hargata/documentation
Hargata/documentation
2024-01-25 17:52:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8c6dd5e343 Updated website 2024-01-25 17:52:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
01b1e8228d Merge branch 'main' into Hargata/documentation 2024-01-25 17:01:31 -07:00
Hargata Softworks
43979d6115
Merge pull request #167 from hargata/Hargata/demo.mode
Demo Mode
2024-01-25 15:02:20 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6a9860e202 added demo mode. 2024-01-25 15:01:56 -07:00
Hargata Softworks
39338e8028
Merge pull request #166 from hargata/Hargata/decimal.odometer
added more screenshots
2024-01-25 14:23:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d1d5351a01 added more screenshots 2024-01-25 14:22:21 -07:00
Hargata Softworks
c9385c7fdd
Merge pull request #165 from hargata/Hargata/decimal.odometer
updated website.
2024-01-25 14:06:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
afaae89af6 updated website. 2024-01-25 14:05:59 -07:00
Hargata Softworks
b9d799cd49
Merge pull request #164 from hargata/Hargata/decimal.odometer
Prevent decimals in odometer fields to error out.
2024-01-25 13:59:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e47c541e08 Prevent decimals in odometer fields to error out. 2024-01-25 13:58:47 -07:00
Hargata Softworks
51ff01d2cd
Merge pull request #162 from hargata/Hargata/locale.gas.fix
more gas fixes due to european locale.
2024-01-25 08:33:25 -07:00
DESKTOP-GENO133\IvanPlex
618399cb09 more gas fixes due to european locale. 2024-01-25 08:31:56 -07:00
Hargata Softworks
b837a2e528
Merge pull request #158 from hargata/Hargata/reminder.email.endpoint
stop method from returning prematurely.
2024-01-24 22:27:32 -07:00
DESKTOP-GENO133\IvanPlex
a1e8b8f9cc stop method from returning prematurely. 2024-01-24 22:26:55 -07:00
Hargata Softworks
787c5da72a
Merge pull request #157 from hargata/Hargata/reminder.email.endpoint
added vehicle information.
2024-01-24 22:24:40 -07:00
DESKTOP-GENO133\IvanPlex
260703be8e added vehicle information. 2024-01-24 22:23:31 -07:00
Hargata Softworks
053801b046
Merge pull request #156 from hargata/Hargata/reminder.email.endpoint
Hargata/reminder.email.endpoint
2024-01-24 21:48:15 -07:00
DESKTOP-GENO133\IvanPlex
db9b1970c5 updated API documentation. 2024-01-24 21:47:05 -07:00
DESKTOP-GENO133\IvanPlex
b153ef5ea5 added more mileage intervals and api endpoint to send out reminder emails. 2024-01-24 21:43:26 -07:00
Hargata Softworks
b54809f399
Merge pull request #155 from hargata/Hargata/zero.chart
Baseline zero charges for bar charts.
2024-01-24 09:07:33 -07:00
DESKTOP-GENO133\IvanPlex
f7f47c54ff added baseline zero costs charge so that bar charts display all months regardless of whether there are costs or not. 2024-01-24 09:03:55 -07:00
Hargata Softworks
92564ae527
Merge pull request #151 from hargata/Hargata/recurring.tax
Hargata/recurring.tax
2024-01-23 22:41:51 -07:00
DESKTOP-GENO133\IvanPlex
52ada8574d add pinned icon for pinned notes. 2024-01-23 22:15:53 -07:00
DESKTOP-GENO133\IvanPlex
013fb67943 Recurring fees. 2024-01-23 21:48:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d86298f502 make tax recurring. 2024-01-23 21:10:59 -07:00
Hargata Softworks
5891b78be0
Merge pull request #149 from hargata/Hargata/pin.notes
added ability to pin notes so that they always show up on top.
2024-01-23 17:48:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a9e3e44f2c added ability to pin notes so that they always show up on top. 2024-01-23 17:46:54 -07:00
Hargata Softworks
0d1c7234e8
Create Collaboration.md 2024-01-23 17:22:07 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e3abe5f209 Merge branch 'main' into Hargata/documentation 2024-01-23 17:15:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b453bfce5b removed auth html. 2024-01-23 17:14:53 -07:00
Hargata Softworks
147a1b03a7
Create Registration.md 2024-01-23 16:57:02 -07:00
Hargata Softworks
3017db5f86
Update Configuring.md 2024-01-23 16:45:34 -07:00
Hargata Softworks
399d0d8058
Update GettingStarted.md 2024-01-23 16:43:52 -07:00
Hargata Softworks
b8c0d4ef67
Update GettingStarted.md 2024-01-23 16:43:03 -07:00
Hargata Softworks
918d086705
Update Configuring.md 2024-01-23 16:39:40 -07:00
Hargata Softworks
ac05acf96b
Update Configuring.md 2024-01-23 16:37:49 -07:00
Hargata Softworks
e7449806c0
Create Configuring.md 2024-01-23 16:36:46 -07:00
Hargata Softworks
baab3213b5
Update GettingStarted.md 2024-01-23 16:19:41 -07:00
Hargata Softworks
d68e9e9589
Create GettingStarted.md 2024-01-23 16:16:38 -07:00
Hargata Softworks
be81f9727a
Merge pull request #146 from hargata/Hargata/auto.reminder.setting
Hargata/auto.reminder.setting
2024-01-23 11:48:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
04b7e7fd38 added setting to auto-insert odometer readings. 2024-01-23 11:46:11 -07:00
DESKTOP-GENO133\IvanPlex
e6b50fafd2 fixed column width when printing vehicle service report. 2024-01-23 09:06:10 -07:00
DESKTOP-GENO133\IvanPlex
d4896a7607 added setting to disable auto reminder refresh for recurring past due reminders. 2024-01-23 08:57:11 -07:00
Hargata Softworks
0b203709fa
Merge pull request #144 from hargata/Hargata/mileageinterval
added 15k mile interval.
2024-01-22 14:56:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
df5faba146 added 15k mile interval. 2024-01-22 14:55:56 -07:00
Hargata Softworks
d8f8b63488
Merge pull request #143 from hargata/Hargata/supply.store
Add Functionality to Requisition Supplies when adding record.
2024-01-22 13:52:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c100fc76ed added requisition ability for plans. 2024-01-22 13:52:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c553d87600 display error message when no supplies are found. 2024-01-22 12:23:04 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b542bd54fb adding requisiton functionality to upgrades and repairs. 2024-01-22 12:04:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4ec11a47a1 added method to requisiton supplies. 2024-01-22 11:47:53 -07:00
DESKTOP-T0O5CDB\DESK-555BD
175ce2be48 added more helper methods. 2024-01-22 11:09:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
aad1655f2e added global parsefloat method which derives from C# culture. 2024-01-22 10:52:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
85eb0b70e6 Merge branch 'main' into Hargata/supply.store 2024-01-22 08:14:59 -07:00
Hargata Softworks
f54e12886a
Merge pull request #142 from hargata/Hargata/fix.chart
fix misleading bar charts.
2024-01-22 08:12:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
80ebe4c292 fix misleading bar charts. 2024-01-22 08:11:29 -07:00
DESKTOP-GENO133\IvanPlex
92b3bc3aea rough draft of supply store. 2024-01-21 21:13:03 -07:00
Hargata Softworks
2cfb82c235
Merge pull request #138 from hargata/Hargata/gas.api
added Gas Post API.
2024-01-21 17:57:57 -07:00
DESKTOP-GENO133\IvanPlex
dd693323d7 added Gas Post API. 2024-01-21 17:56:03 -07:00
Hargata Softworks
5c8f03003e
Merge pull request #134 from hargata/Hargata/post.api
POST API Endpoints
2024-01-21 09:03:26 -07:00
DESKTOP-GENO133\IvanPlex
023fac2ea9 added additional validations to post api endpoints and their documentation. 2024-01-21 08:36:10 -07:00
DESKTOP-GENO133\IvanPlex
9086c26b5e Merge branch 'main' into Hargata/post.api 2024-01-21 08:18:13 -07:00
DESKTOP-GENO133\IvanPlex
0af8e99e61 updated version lmao my bad 2024-01-21 08:15:33 -07:00
DESKTOP-GENO133\IvanPlex
4ca45dd32b added post endpoints for service records, upgrade records and repair records. 2024-01-21 08:14:45 -07:00
DESKTOP-GENO133\IvanPlex
127753ee86 fixed 403 for API Controller. 2024-01-20 18:36:35 -07:00
DESKTOP-GENO133\IvanPlex
30a9411cdd added post methods for tax records. 2024-01-20 17:47:48 -07:00
Hargata Softworks
e801a4a77c
Merge pull request #133 from hargata/Hargata/minor.fixes
Hargata/minor.fixes
2024-01-20 14:41:02 -07:00
DESKTOP-GENO133\IvanPlex
d8c49995ce more math fixes. 2024-01-20 14:38:57 -07:00
DESKTOP-GENO133\IvanPlex
0c93663e51 Fixed a bunch of minor stuff, styling and average mpg calculation. 2024-01-20 14:30:07 -07:00
Hargata Softworks
605ac07594
Update screenshots.md 2024-01-20 13:56:14 -07:00
Hargata Softworks
9a7f2233a0
Merge pull request #130 from hargata/Hargata/to.do
added button to manually push back a recurring reminder record.
2024-01-20 12:11:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1339c427c4 added button to manually push back a recurring reminder record. 2024-01-20 12:10:54 -07:00
Hargata Softworks
8d79804872
Merge pull request #129 from hargata/Hargata/to.do
escape datecreated string
2024-01-20 10:34:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3598bb6adb added method to decode html entities 2024-01-20 10:33:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
50b18a1a71 escape datecreated string 2024-01-20 10:21:13 -07:00
Hargata Softworks
0ab585d4cc
Merge pull request #125 from hargata/Hargata/to.do
V1.0.7 - Planner(Kanban), Recurring Reminders, Odometer Tab
2024-01-20 09:54:10 -07:00
Hargata Softworks
d32b9b879d
Merge pull request #127 from hargata/Hargata/odometer.tab
Hargata/odometer.tab
2024-01-20 09:53:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
594ad38454 added API endpoints 2024-01-20 09:52:16 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ae8a885b4d added odometer tab. 2024-01-20 09:34:31 -07:00
Hargata Softworks
975bbadaae
Merge pull request #126 from hargata/Hargata/recurring.reminder
Hargata/recurring.reminder
2024-01-20 09:02:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
42711bc92a added front end for recurring reminder. 2024-01-20 08:43:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f4538ffabd method to calculate for recurring reminders. 2024-01-20 08:21:06 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d60c3d3ea6 show cancel button. 2024-01-20 07:24:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3cc32e48f4 asks for odometer reading when task is marked as done. 2024-01-20 07:22:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0d4b7d8ee1 clean up and auto convert records. 2024-01-19 21:03:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d8249c7163 Added CSV Import and Export 2024-01-19 20:37:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
60edb65b55 create front end for Planner 2024-01-19 19:34:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
bc2bb3636b added backend for planner. 2024-01-19 14:54:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
58c49c1240 Merge branch 'main' into Hargata/to.do 2024-01-19 10:48:30 -07:00
DESKTOP-GENO133\IvanPlex
2eb9a58aa6 added enums. 2024-01-19 10:27:17 -07:00
Hargata Softworks
b05ded67a9
Merge pull request #123 from hargata/Hargata/gas.enhancement
Gas Enhancements
2024-01-19 09:56:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
76c9473704 added ability to add gas records by Unit Costs and added notes field to gas records. 2024-01-19 09:52:54 -07:00
Hargata Softworks
3909223d2f
Merge pull request #122 from hargata/Hargata/fix.div.byzero
fix divide by zero.
2024-01-19 00:01:58 -07:00
DESKTOP-GENO133\IvanPlex
ab720272da fix divide by zero. 2024-01-19 00:00:13 -07:00
Hargata Softworks
b702a663f4
Merge pull request #120 from hargata/Hargata/mobile.columns
fixed descending option.
2024-01-18 16:51:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1d8baaf423 fixed descending option. 2024-01-18 16:50:53 -07:00
Hargata Softworks
0869c99090
Merge pull request #118 from hargata/Hargata/mobile.columns
Hargata/mobile.columns
2024-01-18 11:16:22 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48b2bab5d6 added github action to push to docker hub 2024-01-18 11:16:06 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0bfa036603 added API controller endpoint documentation for makebackup 2024-01-18 11:12:31 -07:00
DESKTOP-GENO133\IvanPlex
a02bcf94d7 fix overflowing file names in file uploads. 2024-01-18 09:00:14 -07:00
DESKTOP-GENO133\IvanPlex
9e0b45deac fixed view for date on all screens. 2024-01-18 08:12:38 -07:00
Hargata Softworks
1b04faad9c
Merge pull request #116 from hargata/Hargata/fix.mobile.nav
add api endpoint to create backup.
2024-01-17 22:19:03 -07:00
Hargata Softworks
17f5cf6a1d
Update README.md 2024-01-17 22:18:18 -07:00
Hargata Softworks
bf7cffdf8f
Update screenshots.md 2024-01-17 21:21:32 -07:00
Hargata Softworks
a3ba527765
Update screenshots.md 2024-01-17 21:18:47 -07:00
Hargata Softworks
a085b2d87d
Create screenshots.md 2024-01-17 21:15:25 -07:00
DESKTOP-T0O5CDB\DESK-555BD
06f1ce5884 add api endpoint to create backup. 2024-01-17 15:53:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c77f9bce51 no delete option. 2024-01-17 12:33:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cf11c203ce added logging. 2024-01-17 12:25:18 -07:00
Hargata Softworks
4550d33b92
Merge pull request #114 from hargata/Hargata/version
added patreon link and version number.
2024-01-17 12:11:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
503127eb03 added patreon link and version number. 2024-01-17 12:10:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
dbb139dfad updated documentation. 2024-01-17 12:00:05 -07:00
Hargata Softworks
e1a5a871ae
Merge pull request #113 from hargata/Hargata/supplies
Supplies Tab
2024-01-17 11:04:25 -07:00
DESKTOP-GENO133\IvanPlex
0714ec6432 Create and Restore Backups. 2024-01-17 11:02:40 -07:00
DESKTOP-GENO133\IvanPlex
36339a04e1 removed unused using. 2024-01-16 21:11:30 -07:00
DESKTOP-GENO133\IvanPlex
8af9868d2f Added front end for supplies. 2024-01-16 20:38:54 -07:00
DESKTOP-GENO133\IvanPlex
99d5372f25 Added supply record backend. 2024-01-16 09:02:33 -07:00
Hargata Softworks
bd6defe205
Merge pull request #107 from hargata/Hargata/fuel.decimal.settings
named setting better and only call userconfig method once in the gas …
2024-01-16 08:46:15 -07:00
DESKTOP-GENO133\IvanPlex
f5276031c0 fixed z-index for mobile view. 2024-01-16 08:45:53 -07:00
DESKTOP-GENO133\IvanPlex
50ebdf547a default appsettings. 2024-01-16 08:44:31 -07:00
DESKTOP-GENO133\IvanPlex
ba248758cb added empty tab for supplies 2024-01-16 08:44:09 -07:00
DESKTOP-GENO133\IvanPlex
4bde8181b7 enabled functionality to select default tab. 2024-01-15 20:01:34 -07:00
DESKTOP-GENO133\IvanPlex
d0d733b7d2 moved settings around. 2024-01-15 18:47:04 -07:00
DESKTOP-GENO133\IvanPlex
e377f6c8d0 added config to hide tabs that are not visible. 2024-01-15 17:38:25 -07:00
DESKTOP-GENO133\IvanPlex
2fafe1101c named setting better and only call userconfig method once in the gas tab so it doesn't make as much calls to the cache. 2024-01-15 13:26:57 -07:00
Hargata Softworks
56a315524a
Merge pull request #106 from hargata/Hargata/fuel.decimal.settings
add settings to toggle fuel decimals from three to two.
2024-01-15 13:08:40 -07:00
DESKTOP-GENO133\IvanPlex
244a164891 updated default app settings. 2024-01-15 13:05:09 -07:00
DESKTOP-GENO133\IvanPlex
2ec0c1d465 add settings to toggle fuel decimals from three to two. 2024-01-15 13:02:48 -07:00
Hargata Softworks
239ca73a64
Merge pull request #102 from ThisIsQasim/patch-1
Add ARM64 images
2024-01-15 10:13:41 -07:00
Hargata Softworks
b03460d6bb
Merge pull request #105 from hargata/Hargata/qol
Quality of Life Improvements.
2024-01-15 10:10:01 -07:00
DESKTOP-GENO133\IvanPlex
d2686949c5 added additional gas columns to API controller. 2024-01-15 10:05:04 -07:00
DESKTOP-GENO133\IvanPlex
971242b015 updated gas sample. 2024-01-15 10:01:46 -07:00
DESKTOP-GENO133\IvanPlex
bd3b821226 added missedfuelup and partialfuelup to csv export and import. 2024-01-15 09:54:05 -07:00
DESKTOP-GENO133\IvanPlex
6ffa856795 defaults input to today's date but only if there aren't any dates provided. 2024-01-15 09:32:15 -07:00
DESKTOP-GENO133\IvanPlex
6895d2060d FIxed bug caused by doubling of upgrade sums. made table headers sticky so that they stay in position when scrolled past. 2024-01-15 09:21:15 -07:00
Qasim Mehmood
d413a06b87
remove test branch 2024-01-15 16:30:43 +05:00
Qasim Mehmood
263000f0ae
renable image push 2024-01-15 16:29:13 +05:00
Qasim Mehmood
578f6ab62a
Update Dockerfile 2024-01-15 16:27:32 +05:00
Qasim Mehmood
469b625989
disable push 2024-01-15 16:23:26 +05:00
Qasim Mehmood
95a630b600
cross-compile inside the container 2024-01-15 16:22:22 +05:00
Qasim Mehmood
5f576a2792
Update docker-image.yml 2024-01-15 16:17:45 +05:00
Qasim Mehmood
f5af92da93
Add ARM64 images 2024-01-15 16:06:00 +05:00
Hargata Softworks
4ccfcd6be7
Merge pull request #99 from hargata/Hargata/scrollposition
quick fix
2024-01-15 00:05:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
917d093bcf quick fix 2024-01-15 00:04:41 -07:00
Hargata Softworks
c916771815
Update README.md 2024-01-14 21:10:38 -07:00
Hargata Softworks
d4517ac986
Merge pull request #93 from hargata/Hargata/scrollposition
Hargata/scrollposition
2024-01-14 18:09:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
040cc6adf9 select today's date as default for quality of life. 2024-01-14 18:08:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ec740089fe preserve the scroll position. 2024-01-14 17:51:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e29e14bc3e default values in environment variables so people stop getting Http 500. 2024-01-14 17:21:39 -07:00
Hargata Softworks
592baa4c6e
Update LICENSE 2024-01-14 14:49:55 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5e5c9c96b4 Updated readme, env file and license. 2024-01-14 13:37:10 -07:00
Hargata Softworks
487f64e459
Merge pull request #84 from hargata/Hargata/users
Users Management
2024-01-14 13:14:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d67aeaa333 removed fueleconomy from fuelly mapper. 2024-01-14 13:11:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fb5cdfce29 Merge branch 'main' into Hargata/users
# Conflicts:
#	Views/Vehicle/Index.cshtml
2024-01-14 13:08:37 -07:00
DESKTOP-GENO133\IvanPlex
6e5cfde2cf only allow notification if smtp server is setup. 2024-01-14 12:13:37 -07:00
DESKTOP-GENO133\IvanPlex
7122f4ac0d renamed report and make it default tab. 2024-01-14 11:51:57 -07:00
DESKTOP-GENO133\IvanPlex
2fb24b8b65 Merge branch 'Hargata/user.config' into Hargata/users
# Conflicts:
#	Helper/ConfigHelper.cs
#	Views/Home/Index.cshtml
2024-01-14 11:20:02 -07:00
DESKTOP-GENO133\IvanPlex
6540d96d4d fixed admin view even more. 2024-01-14 11:17:51 -07:00
DESKTOP-GENO133\IvanPlex
62e196b97d fix admin view. 2024-01-14 11:12:09 -07:00
DESKTOP-GENO133\IvanPlex
4975861710 moved things around added delay admin page styling. 2024-01-14 10:52:42 -07:00
DESKTOP-GENO133\IvanPlex
8d74799099 replaced IConfiguration injection with IConfigHelper 2024-01-14 09:54:13 -07:00
DESKTOP-GENO133\IvanPlex
c58b4552b2 hide settings page from user. 2024-01-13 23:43:38 -07:00
DESKTOP-GENO133\IvanPlex
8c6920afab enable users to have their own config file. 2024-01-13 23:13:11 -07:00
Hargata Softworks
b77fd2c1c7
Merge pull request #83 from hargata/Hargata/missing.mobile.buttons
Add Missing Buttons in Mobile View.
2024-01-13 22:31:39 -07:00
Hargata Softworks
2035d6f6e2
Merge pull request #86 from hargata/Hargata/fuel.import
fix mapper.
2024-01-13 22:31:14 -07:00
DESKTOP-GENO133\IvanPlex
e58454ef5d fix mapper. 2024-01-13 22:30:11 -07:00
DESKTOP-GENO133\IvanPlex
915eb1722d added confighelper 2024-01-13 22:29:14 -07:00
DESKTOP-GENO133\IvanPlex
4f706d3e93 filtered out vehicles not owned by the user when accessing via API. 2024-01-13 21:35:23 -07:00
DESKTOP-GENO133\IvanPlex
d80f0dcb8f cleaned up unused methods. 2024-01-13 21:33:13 -07:00
DESKTOP-GENO133\IvanPlex
2ae334d06d added functions to add and remove collaborators. 2024-01-13 21:18:58 -07:00
DESKTOP-GENO133\IvanPlex
4388df71f3 added action filter attribute 2024-01-13 20:13:12 -07:00
DESKTOP-GENO133\IvanPlex
c972f9c8a2 added collaborator view. 2024-01-13 18:19:52 -07:00
DESKTOP-GENO133\IvanPlex
90fa6ad5fc fixed method to delete user access. 2024-01-13 17:55:02 -07:00
DESKTOP-GENO133\IvanPlex
a1b2b40abe reshaped user access object, added 401 page. 2024-01-13 17:49:48 -07:00
DESKTOP-GENO133\IvanPlex
00fd499805 added user logic. 2024-01-13 16:34:39 -07:00
DESKTOP-GENO133\IvanPlex
8f3f71772b added data access methods for user access. 2024-01-13 15:44:29 -07:00
DESKTOP-GENO133\IvanPlex
08104eef2a moved field around. 2024-01-13 12:52:26 -07:00
DESKTOP-GENO133\IvanPlex
8d989ee81c added forgot password feature. 2024-01-13 12:50:55 -07:00
DESKTOP-GENO133\IvanPlex
2247b1b1db added ability to notify user that they have a registration token. 2024-01-13 11:48:20 -07:00
DESKTOP-GENO133\IvanPlex
249ad938f6 added missing edit vehicle button and close button for modal in mobile view. 2024-01-13 11:16:14 -07:00
DESKTOP-GENO133\IvanPlex
c9d60910e5 added scaffolding for email sending methods 2024-01-13 11:12:37 -07:00
DESKTOP-GENO133\IvanPlex
03b89786ec make email address required for token generation and user registration. 2024-01-12 23:54:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8815009b04 added token based registration. 2024-01-12 18:37:51 -07:00
Hargata Softworks
bb4a8f7f83
Merge pull request #79 from hargata/Hargata/vehicle.history
order by chronological and then by odometer
2024-01-12 15:26:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c7730d1775 fixed print stylings on iphone. 2024-01-12 15:26:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7a3ef0fa7 order by chronological and then by odometer 2024-01-12 15:17:46 -07:00
Hargata Softworks
d525e2195c
Merge pull request #75 from hargata/Hargata/vehicle.history
added front end stuff for consolidated vehicle maintenance record.
2024-01-12 14:55:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cb73be0e43 handle 0 MPG and styling bug fixes. 2024-01-12 14:54:42 -07:00
Hargata Softworks
a06bdbff88
Merge pull request #73 from hargata/Hargata/nonroot.user
added volume for temp folder so that the container can run as non-roo…
2024-01-12 14:27:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0de6ab7547 fix printing stuff. 2024-01-12 14:26:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5b54b8113e added more stylings. 2024-01-12 14:19:41 -07:00
DESKTOP-GENO133\IvanPlex
4d804803a8 added front end stuff for consolidated vehicle maintenance record. 2024-01-12 12:52:01 -07:00
DESKTOP-GENO133\IvanPlex
2434245c84 added volume for temp folder so that the container can run as non-root user. 2024-01-12 07:17:40 -07:00
Hargata Softworks
d00e6e252d
Merge pull request #68 from hargata/Hargata/better.mobile
make it more mobile friendly.
2024-01-11 23:10:22 -07:00
DESKTOP-GENO133\IvanPlex
e006c158fc full screen navbar for small devices. 2024-01-11 23:04:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
75d610200b make it more mobile friendly. 2024-01-11 20:52:55 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f696030ac2 fixed month order in reports page. 2024-01-11 18:58:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
20190642a8 fixed login viewport height. 2024-01-11 14:34:43 -07:00
Hargata Softworks
5d2068bd78
Merge pull request #64 from hargata/Hargata/pwa
Add PWA Manifests.
2024-01-11 14:20:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3a01944e30 resized screenshot 2024-01-11 14:14:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
014b6bce8d added pwa for android and iphone. 2024-01-11 13:53:02 -07:00
Hargata Softworks
8f69399f1b
Merge pull request #63 from hargata/Hargata/reports.improvement
Hargata/reports.improvement
2024-01-11 11:38:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
200aa79dac Merge branch 'Hargata/consolidated.report' into Hargata/reports.improvement 2024-01-11 11:37:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
dfb6f69d69 removed "All" checkbox on reports page. 2024-01-11 11:26:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cd2fb2dd21 quick fix. 2024-01-11 09:27:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a396eb4f72 Updated colors again. 2024-01-11 09:22:57 -07:00
Hargata Softworks
46675c944f
Merge pull request #57 from hargata/Hargata/consolidated.report
Updated Chart Colors.
2024-01-10 20:17:00 -07:00
Hargata Softworks
ff7ad04f0b
Merge pull request #56 from hargata/Hargata/missed.fuelupimport
add missed_fuelup column to csv imports.
2024-01-10 20:15:43 -07:00
DESKTOP-GENO133\IvanPlex
d69ede1447 add missed_fuelup column to csv imports. 2024-01-10 20:14:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b477cf9d51 Updated Chart Colors. 2024-01-10 14:54:46 -07:00
Hargata Softworks
ad8c27a2e6
Merge pull request #55 from hargata/Hargata/consolidated.report
fixed for real.
2024-01-10 14:43:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
52f9ae7ea1 fixed for real. 2024-01-10 14:42:58 -07:00
Hargata Softworks
ed1066662e
Merge pull request #54 from hargata/Hargata/consolidated.report
Hargata/consolidated.report
2024-01-10 14:30:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e3bf6f03d7 Make charts responsive to params. 2024-01-10 14:04:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8cc2ceecc6 Merge branch 'main' into Hargata/consolidated.report 2024-01-10 09:23:21 -07:00
Hargata Softworks
9837172774
Merge pull request #52 from hargata/Hargata/missed.fuelup
added missed fuel up feature.
2024-01-10 05:37:44 -07:00
DESKTOP-GENO133\IvanPlex
fba2a6dc68 added missed fuel up feature. 2024-01-10 05:36:42 -07:00
Hargata Softworks
a467ba08c6
Merge pull request #51 from hargata/Hargata.international.numbers
added support for international number formats.
2024-01-10 05:11:48 -07:00
DESKTOP-GENO133\IvanPlex
7bfbb49efb added support for international number formats. 2024-01-10 05:10:30 -07:00
Hargata Softworks
7e4f5807d2
Merge pull request #49 from hargata/Hargata/named.notes
added truncating function.
2024-01-09 22:16:35 -07:00
DESKTOP-GENO133\IvanPlex
d5aee08f69 added truncating function. 2024-01-09 22:15:53 -07:00
Hargata Softworks
080d50c6bc
Merge pull request #48 from hargata/Hargata/named.notes
Hargata/named.notes
2024-01-09 21:49:04 -07:00
DESKTOP-GENO133\IvanPlex
3105622d63 added csv exports for gas and tax tab. 2024-01-09 21:41:58 -07:00
DESKTOP-GENO133\IvanPlex
eac181ff20 removed obsolete function. 2024-01-09 21:13:33 -07:00
DESKTOP-GENO133\IvanPlex
2a732cb343 made notes named and easier to traverse. 2024-01-09 21:10:48 -07:00
Hargata Softworks
da5e97143e
Merge pull request #44 from hargata/Hargata/api
Add GET API Endpoints.
2024-01-09 19:00:12 -07:00
DESKTOP-GENO133\IvanPlex
9bdce69a2a added api documentation. 2024-01-09 18:57:55 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8b1db34860 added routes for API methods. 2024-01-09 18:26:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3c67d37e12 Added get methods to API. 2024-01-09 18:21:31 -07:00
DESKTOP-GENO133\IvanPlex
b73c2e979f draft for ze api controller. 2024-01-09 15:18:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a52fc29067 github pages. 2024-01-09 14:44:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c6774d4be7 Added import/export functionality for upgrades, service records, and repair records. 2024-01-09 13:51:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1271b3517d make report container smaller to accomodate for consolidated report below. 2024-01-09 12:59:25 -07:00
404 changed files with 41666 additions and 13511 deletions

2
.env
View File

@ -1,2 +0,0 @@
LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8

50
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,50 @@
# Contributing Guidelines
## Asking Questions
Before submitting a question, please check the [Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting) and search through existing issues.
Ideally, the Issues tab should only consist of bug reports and feature requests instead of debugging your LubeLogger installation.
## Feature Requests
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
Read [this](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose) to better understand the scope and purpose of this project.
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
It is not and should not be used for the following:
- Project Management Software(e.g.: Jira)
- Budget Management(e.g: YNAB/Actual)
- Inventory Management
Before submitting a feature request, please consider the following:
- Will this feature benefit more than just a niche subset of users
- Will this feature result in scope creep
- Has this feature already been requested/exist
## Bug Reports
Please fill out the issue template for bug reports.
## Security Vulnerabilities
Contact us via [Email](mailto:hargatasoftworks@gmail.com)
## Submitting Pull Requests
Pull Requests are welcome, but please consider the following:
CI/CD, Bugs, Tech-Debt Related PRs:
- Make sure changes are not breaking.
- If any dependencies are added, they must be cross-platform compatible.
- Please test changes thoroughly.
Feature Related PRs:
- Have you requested this feature in the Issues tab
- Will this feature benefit more than just a niche subset of users
- Will this feature result in scope creep
- Do these changes fix a root cause or is it simply a workaround
Ideally, you should first submit a feature request in the Issues tab before submitting a PR just in case it's something we are already working on.
Note the following:
We(the maintainers) are not responsible for cleaning up, testing, or fixing your changes.
If your changes doesn't have a wide-ranging use-case, has not been thoroughly tested, or cannot be easily maintained, we won't merge your PR.

14
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,14 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: lubelogger
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,30 @@
---
name: Bug report
about: Report a bug
title: "[BUG]"
labels: bug
assignees: ''
---
**Checklist**
Please make sure you have performed the following steps before opening a new bug ticket, change `[ ]` to `[x]` to mark it as done
- [ ] I have read and tried the steps outlined in the [Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting)
- [ ] I have searched through existing issues.
**Description**
<!-- Describe the bug below this line -->
**Platform**
- [ ] Docker Image
- [ ] Windows/Linux Standalone Executable
**Browser Console Errors(F12)**
<!-- Attach a screenshot or codeblock containing the browser console error -->
**App/Container Console Error**
<!-- Attach a screenshot or codeblock containing the app/container console error -->
**Screenshots(optional)**
<!-- Attach a screenshot describing the bug -->

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE REQUEST]"
labels: enhancement
assignees: ''
---
**Checklist**
Please make sure you have performed the following steps before submitting a new feature request, change `[ ]` to `[x]` to mark it as done
- [ ] I have read the [Contributing Guidelines](https://github.com/hargata/lubelog/blob/main/.github/CONTRIBUTING.md) and [Scope and Purpose](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose)
- [ ] I have searched through existing issues.
**Description**
<!-- Describe the feature request below this line -->

View File

@ -0,0 +1,51 @@
name: Build and Push Image to Dockerhub and GHCR
on:
push:
branches: ["main"]
release:
types: ["published"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: "${{ secrets.DH_USER }}"
password: "${{ secrets.DH_PASS }}"
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: "hargata"
password: "${{ secrets.GHCR_PAT }}"
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
context: workflow
images: |
hargata/lubelogger
ghcr.io/hargata/lubelogger
tags: |
type=edge,branch=main
type=ref,event=tag
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -1,31 +0,0 @@
name: Docker Image CI
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: "hargata"
password: "${{ secrets.GHCR_PAT }}"
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
push: true
tags: |
ghcr.io/hargata/lubelogger:latest

9
.gitignore vendored
View File

@ -1,9 +1,6 @@
.vs/
bin/
obj/
wwwroot/images/
cartracker.db
data/cartracker.db
wwwroot/documents/
wwwroot/temp/
wwwroot/imports/
data/
CarCareTracker.csproj.user
Properties/launchSettings.json

View File

@ -11,8 +11,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.14.0" />
<PackageReference Include="Npgsql" Version="9.0.4" />
</ItemGroup>
</Project>

View File

@ -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>

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

2730
Controllers/APIController.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,118 @@
using CarCareTracker.Helper;
using CarCareTracker.Logic;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
[Authorize(Roles = nameof(UserData.IsAdmin))]
public class AdminController : Controller
{
private ILoginLogic _loginLogic;
private IUserLogic _userLogic;
private IConfigHelper _configHelper;
public AdminController(ILoginLogic loginLogic, IUserLogic userLogic, IConfigHelper configHelper)
{
_loginLogic = loginLogic;
_userLogic = userLogic;
_configHelper = configHelper;
}
public IActionResult Index()
{
var viewModel = new AdminViewModel
{
Users = _loginLogic.GetAllUsers().OrderBy(x=>x.Id).ToList(),
Tokens = _loginLogic.GetAllTokens()
};
return View(viewModel);
}
public IActionResult GetTokenPartialView()
{
var viewModel = _loginLogic.GetAllTokens();
return PartialView("_Tokens", viewModel);
}
public IActionResult GetUserPartialView()
{
var viewModel = _loginLogic.GetAllUsers().OrderBy(x => x.Id).ToList();
return PartialView("_Users", viewModel);
}
public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
{
if (emailAddress.Contains(","))
{
string[] emailAddresses = emailAddress.Split(',');
foreach(string emailAdd in emailAddresses)
{
var trimmedEmail = emailAdd.Trim();
if (!string.IsNullOrWhiteSpace(trimmedEmail))
{
var result = _loginLogic.GenerateUserToken(emailAdd.Trim(), autoNotify);
if (!result.Success)
{
//if fail, return prematurely
return Json(result);
}
}
}
var successResponse = OperationResponse.Succeed("Token Generated!");
return Json(successResponse);
} else
{
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
return Json(result);
}
}
[HttpPost]
public IActionResult DeleteToken(int tokenId)
{
var result = _loginLogic.DeleteUserToken(tokenId);
return Json(result);
}
[HttpPost]
public IActionResult DeleteUser(int userId)
{
var result =_userLogic.DeleteAllAccessToUser(userId)
&& _configHelper.DeleteUserConfig(userId)
&& _loginLogic.DeleteUser(userId)
&& _userLogic.DeleteAllHouseholdByChildUserId(userId)
&& _userLogic.DeleteAllHouseholdByParentUserId(userId);
return Json(result);
}
[HttpPost]
public IActionResult UpdateUserAdminStatus(int userId, bool isAdmin)
{
var result = _loginLogic.MakeUserAdmin(userId, isAdmin);
return Json(result);
}
[HttpGet]
public IActionResult GetUserHouseholdModal(int userId)
{
var households = _userLogic.GetHouseholdForParentUserId(userId);
if (households.Any())
{
households = households.OrderBy(x => x.UserName).ToList();
}
var viewModel = new UserHouseholdAdminViewModel { Households = households, ParentUserId = userId };
return PartialView("_AdminUserHouseholdModal", viewModel);
}
[HttpPost]
public IActionResult RemoveUserFromHousehold(int parentUserId, int childUserId)
{
var result = _userLogic.DeleteUserFromHousehold(parentUserId, childUserId);
return Json(result);
}
[HttpPost]
public IActionResult AddUserToHousehold(int parentUserId, string username)
{
var result = _userLogic.AddUserToHousehold(parentUserId, username);
return Json(result);
}
[HttpPost]
public IActionResult ModifyUserHouseholdPermissions(int parentUserId, int childUserId, List<HouseholdPermission> permissions)
{
var result = _userLogic.UpdateUserHousehold(parentUserId, childUserId, permissions);
return Json(result);
}
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public class ErrorController : Controller
{
public IActionResult Unauthorized()
{
if (User.IsInRole("APIAuth"))
{
Response.StatusCode = 403;
return new EmptyResult();
}
return View("401");
}
}
}

View File

@ -1,12 +1,5 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using LiteDB;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
using System.Drawing;
using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using CarCareTracker.Helper;
using Microsoft.AspNetCore.Authorization;
@ -33,6 +26,26 @@ namespace CarCareTracker.Controllers
return Json(fileName);
}
[HttpPost]
public IActionResult HandleTranslationFileUpload(IFormFile file)
{
var originalFileName = Path.GetFileNameWithoutExtension(file.FileName);
if (originalFileName == "en_US")
{
return Json(OperationResponse.Failed("The translation file name en_US is reserved."));
}
var fileName = UploadFile(file);
//move file from temp to translation folder.
var uploadedFilePath = _fileHelper.MoveFileFromTemp(fileName, "translations/");
//rename uploaded file so that it preserves original name.
if (!string.IsNullOrWhiteSpace(uploadedFilePath))
{
var result = _fileHelper.RenameFile(uploadedFilePath, originalFileName);
return Json(OperationResponse.Conditional(result));
}
return Json(OperationResponse.Failed());
}
[HttpPost]
public IActionResult HandleMultipleFileUpload(List<IFormFile> file)
{
@ -40,22 +53,35 @@ namespace CarCareTracker.Controllers
foreach (IFormFile fileToUpload in file)
{
var fileName = UploadFile(fileToUpload);
uploadedFiles.Add(new UploadedFiles { Name = fileToUpload.FileName, Location = fileName});
uploadedFiles.Add(new UploadedFiles { Name = fileToUpload.FileName, Location = fileName, IsPending = true});
}
return Json(uploadedFiles);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public ActionResult DeleteFiles(string fileLocation)
public IActionResult DeleteFiles(string fileLocation)
{
var result = _fileHelper.DeleteFile(fileLocation);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult MakeBackup()
{
var result = _fileHelper.MakeBackup();
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult RestoreBackup(string fileName)
{
var result = _fileHelper.RestoreBackup(fileName);
return Json(result);
}
private string UploadFile(IFormFile fileToUpload)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
@ -66,5 +92,23 @@ namespace CarCareTracker.Controllers
}
return Path.Combine("/", uploadDirectory, fileName);
}
public IActionResult UploadCoordinates(List<string> coordinates)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + ".csv";
string filePath = Path.Combine(uploadPath, fileName);
string fileData = string.Join("\r\n", coordinates);
System.IO.File.WriteAllText(filePath, fileData);
var uploadedFile = new UploadedFiles { Name = "coordinates.csv", Location = Path.Combine("/", uploadDirectory, fileName), IsPending = true };
return Json(uploadedFile);
}
public IActionResult PreviewFile(string fileName, string fileLocation)
{
var viewModel = new UploadedFiles { Name = fileName, Location = fileLocation };
return PartialView("_AttachmentPreview", viewModel);
}
}
}

View File

@ -1,14 +1,13 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using LiteDB;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
using System.Drawing;
using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using CarCareTracker.Helper;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using CarCareTracker.Logic;
using System.Globalization;
using System.Text.Json;
namespace CarCareTracker.Controllers
{
@ -17,77 +16,681 @@ namespace CarCareTracker.Controllers
{
private readonly ILogger<HomeController> _logger;
private readonly IVehicleDataAccess _dataAccess;
private readonly IUserLogic _userLogic;
private readonly ILoginLogic _loginLogic;
private readonly IVehicleLogic _vehicleLogic;
private readonly IFileHelper _fileHelper;
private readonly IConfiguration _config;
public HomeController(ILogger<HomeController> logger, IVehicleDataAccess dataAccess, IFileHelper fileHelper, IConfiguration configuration)
private readonly IConfigHelper _config;
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
private readonly ITranslationHelper _translationHelper;
private readonly IMailHelper _mailHelper;
public HomeController(ILogger<HomeController> logger,
IVehicleDataAccess dataAccess,
IUserLogic userLogic,
ILoginLogic loginLogic,
IVehicleLogic vehicleLogic,
IConfigHelper configuration,
IFileHelper fileHelper,
IExtraFieldDataAccess extraFieldDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IReminderHelper reminderHelper,
ITranslationHelper translationHelper,
IMailHelper mailHelper)
{
_logger = logger;
_dataAccess = dataAccess;
_fileHelper = fileHelper;
_config = configuration;
_userLogic = userLogic;
_fileHelper = fileHelper;
_extraFieldDataAccess = extraFieldDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
_loginLogic = loginLogic;
_vehicleLogic = vehicleLogic;
_translationHelper = translationHelper;
_mailHelper = mailHelper;
}
public IActionResult Index(string tab = "garage")
private int GetUserID()
{
return View(model: tab);
return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier));
}
public IActionResult Index()
{
return View();
}
[Route("/kiosk")]
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
{
try {
var viewModel = new KioskViewModel
{
Exclusions = string.IsNullOrWhiteSpace(exclusions) ? new List<int>() : exclusions.Split(',').Select(x => int.Parse(x)).ToList(),
KioskMode = kioskMode
};
return View(viewModel);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return View(new KioskViewModel());
}
}
[HttpPost]
public IActionResult KioskContent(KioskViewModel kioskParameters)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
vehiclesStored.RemoveAll(x => kioskParameters.Exclusions.Contains(x.Id));
var userConfig = _config.GetUserConfig(User);
if (userConfig.HideSoldVehicles)
{
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
}
switch (kioskParameters.KioskMode)
{
case KioskMode.Vehicle:
{
var kioskResult = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", kioskResult);
}
case KioskMode.Plan:
{
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
return PartialView("_KioskPlan", kioskResult);
}
case KioskMode.Reminder:
{
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
return PartialView("_KioskReminder", kioskResult);
}
}
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", result);
}
public IActionResult Garage()
{
var vehiclesStored = _dataAccess.GetVehicles();
return PartialView("_GarageDisplay", vehiclesStored);
}
public IActionResult Settings()
{
var userConfig = new UserConfig
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)])
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
var vehicleViewModels = vehiclesStored.Select(x =>
{
var vehicleVM = new VehicleViewModel
{
Id = x.Id,
ImageLocation = x.ImageLocation,
Year = x.Year,
Make = x.Make,
Model = x.Model,
LicensePlate = x.LicensePlate,
SoldDate = x.SoldDate,
IsElectric = x.IsElectric,
IsDiesel = x.IsDiesel,
UseHours = x.UseHours,
OdometerOptional = x.OdometerOptional,
ExtraFields = x.ExtraFields,
Tags = x.Tags,
DashboardMetrics = x.DashboardMetrics,
VehicleIdentifier = x.VehicleIdentifier
};
//dashboard metrics
if (x.DashboardMetrics.Any())
{
var vehicleRecords = _vehicleLogic.GetVehicleRecords(x.Id);
var userConfig = _config.GetUserConfig(User);
var distanceUnit = x.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.Default))
{
vehicleVM.LastReportedMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
vehicleVM.HasReminders = _vehicleLogic.GetVehicleHasUrgentOrPastDueReminders(x.Id, vehicleVM.LastReportedMileage);
}
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.CostPerMile))
{
var vehicleTotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var totalDistance = maxMileage - minMileage;
vehicleVM.CostPerMile = totalDistance != default ? vehicleTotalCost / totalDistance : 0.00M;
vehicleVM.DistanceUnit = distanceUnit;
}
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.TotalCost))
{
vehicleVM.TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
}
}
return vehicleVM;
}).ToList();
return PartialView("_GarageDisplay", vehicleViewModels);
}
public IActionResult Calendar()
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
return PartialView("_Calendar", reminders);
}
public IActionResult ViewCalendarReminder(int reminderId)
{
var reminder = _reminderRecordDataAccess.GetReminderRecordById(reminderId);
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(new List<ReminderRecord> { reminder }, 0, DateTime.Now).FirstOrDefault();
return PartialView("_ReminderRecordCalendarModal", reminderUrgency);
}
public async Task<IActionResult> Settings()
{
var userConfig = _config.GetUserConfig(User);
var languages = _fileHelper.GetLanguages();
var viewModel = new SettingsViewModel
{
UserConfig = userConfig,
UILanguages = languages
};
return PartialView("_Settings", userConfig);
return PartialView("_Settings", viewModel);
}
public async Task<IActionResult> Sponsors()
{
try
{
var httpClient = new HttpClient();
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
return PartialView("_Sponsors", sponsorsData);
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
return PartialView("_Sponsors", new Sponsors());
}
}
[HttpPost]
public IActionResult WriteToSettings(UserConfig userConfig)
{
//retrieve existing userConfig.
var existingConfig = _config.GetUserConfig(User);
//copy over stuff that persists
userConfig.UserColumnPreferences = existingConfig.UserColumnPreferences;
var result = _config.SaveUserConfig(User, userConfig);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult GetExtraFieldsModal(int importMode = 0)
{
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(importMode);
if (recordExtraFields.Id != importMode)
{
recordExtraFields.Id = importMode;
}
return PartialView("_ExtraFields", recordExtraFields);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult UpdateExtraFields(RecordExtraField record)
{
try
{
if (!System.IO.File.Exists(StaticHelper.UserConfigPath))
{
var result = _extraFieldDataAccess.SaveExtraFields(record);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(record.Id);
return PartialView("_ExtraFields", recordExtraFields);
}
[HttpPost]
public IActionResult GenerateTokenForUser()
{
try
{
//get current user email address.
var emailAddress = User.FindFirstValue(ClaimTypes.Email);
if (!string.IsNullOrWhiteSpace(emailAddress))
{
//if file doesn't exist it might be because it's running on a mounted volume in docker.
System.IO.File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
var result = _loginLogic.GenerateTokenForEmailAddress(emailAddress, false);
return Json(result);
}
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
if (existingUserConfig is not null)
return Json(false);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(false);
}
}
[HttpPost]
public IActionResult UpdateUserAccount(LoginModel userAccount)
{
try
{
var userId = GetUserID();
if (userId > 0)
{
//copy over settings that are off limits on the settings page.
userConfig.EnableAuth = existingUserConfig.EnableAuth;
userConfig.UserNameHash = existingUserConfig.UserNameHash;
userConfig.UserPasswordHash = existingUserConfig.UserPasswordHash;
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
return Json(result);
}
return Json(OperationResponse.Failed());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(OperationResponse.Failed());
}
}
[HttpGet]
public IActionResult GetUserAccountInformationModal()
{
var emailAddress = User.FindFirstValue(ClaimTypes.Email);
var userName = User.Identity.Name;
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
}
[HttpGet]
public IActionResult GetHouseholdModal()
{
var households = _userLogic.GetHouseholdForParentUserId(GetUserID());
if (households.Any())
{
households = households.OrderBy(x => x.UserName).ToList();
}
var userhouseholds = _userLogic.GetHouseholdForChildUserId(GetUserID());
if (userhouseholds.Any())
{
userhouseholds = userhouseholds.OrderBy(x => x.UserName).ToList();
}
var viewModel = new UserHouseholdUserViewModel()
{
Households = households,
UserHouseholds = userhouseholds
};
return PartialView("_UserHouseholdModal", viewModel);
}
[HttpPost]
public IActionResult RemoveUserFromHousehold(int userId)
{
var result = _userLogic.DeleteUserFromHousehold(GetUserID(), userId);
return Json(result);
}
[HttpPost]
public IActionResult LeaveHousehold(int userId)
{
var result = _userLogic.DeleteUserFromHousehold(userId, GetUserID());
return Json(result);
}
[HttpPost]
public IActionResult ModifyUserHouseholdPermissions(int userId, List<HouseholdPermission> permissions)
{
var result = _userLogic.UpdateUserHousehold(GetUserID(), userId, permissions);
return Json(result);
}
[HttpPost]
public IActionResult AddUserToHousehold(string username)
{
var result = _userLogic.AddUserToHousehold(GetUserID(), username);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetRootAccountInformationModal()
{
var userName = User.Identity.Name;
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetTranslatorEditor(string userLanguage)
{
var translationData = _translationHelper.GetTranslations(userLanguage);
return PartialView("_TranslationEditor", translationData);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult SaveTranslation(IFormFile file)
{
var userLanguage = Path.GetFileNameWithoutExtension(file.FileName);
var translationData = new Dictionary<string, string>();
using (var sReader = new StreamReader(file.OpenReadStream()))
{
var sData = sReader.ReadToEnd();
translationData = JsonSerializer.Deserialize<Dictionary<string, string>>(sData);
}
var result = _translationHelper.SaveTranslation(userLanguage, translationData);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult ExportTranslation(IFormFile file)
{
var result = string.Empty;
using (var sReader = new StreamReader(file.OpenReadStream()))
{
var fileName = $"{Guid.NewGuid()}.json";
var sData = sReader.ReadToEnd();
result = _fileHelper.WriteFileToTemp(fileName, sData);
}
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> GetAvailableTranslations()
{
try
{
var httpClient = new HttpClient();
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
return PartialView("_Translations", translations);
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return PartialView("_Translations", new Translations());
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> DownloadTranslation(string continent, string name)
{
try
{
var httpClient = new HttpClient();
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath(continent, name)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(name, translationData);
if (!result.Success)
{
return Json(false);
}
}
else
{
_logger.LogError($"Unable to download translation: {name}");
return Json(false);
}
return Json(true);
}
catch (Exception ex)
{
_logger.LogError($"Unable to download translation: {ex.Message}");
return Json(false);
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> DownloadAllTranslations()
{
try
{
var httpClient = new HttpClient();
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
int translationsDownloaded = 0;
foreach (string translation in translations.Asia)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Asia", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Africa)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Africa", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Europe)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Europe", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.NorthAmerica)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("NorthAmerica", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.SouthAmerica)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("SouthAmerica", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Oceania)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Oceania", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
if (translationsDownloaded > 0)
{
return Json(OperationResponse.Succeed($"{translationsDownloaded} Translations Downloaded"));
} else
{
userConfig.EnableAuth = false;
userConfig.UserNameHash = string.Empty;
userConfig.UserPasswordHash = string.Empty;
return Json(OperationResponse.Failed("No Translations Downloaded"));
}
System.IO.File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(userConfig));
return Json(true);
} catch (Exception ex)
}
catch (Exception ex)
{
_logger.LogError(ex, "Error on saving config file.");
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return Json(OperationResponse.Failed());
}
}
public ActionResult GetVehicleSelector(int vehicleId)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
if (vehicleId != default)
{
vehiclesStored.RemoveAll(x => x.Id == vehicleId);
}
var userConfig = _config.GetUserConfig(User);
if (userConfig.HideSoldVehicles)
{
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
}
return PartialView("_VehicleSelector", vehiclesStored);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetCustomWidgetEditor()
{
if (_config.GetCustomWidgetsEnabled())
{
var customWidgetData = _fileHelper.GetWidgets();
return PartialView("_WidgetEditor", customWidgetData);
}
return Json(string.Empty);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult SaveCustomWidgets(string widgetsData)
{
if (_config.GetCustomWidgetsEnabled())
{
var saveResult = _fileHelper.SaveWidgets(widgetsData);
return Json(saveResult);
}
return Json(false);
}
public IActionResult Privacy()
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult DeleteCustomWidgets()
{
return View();
if (_config.GetCustomWidgetsEnabled())
{
var deleteResult = _fileHelper.DeleteWidgets();
return Json(deleteResult);
}
return Json(false);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult GetLocaleSample(string locale, string dtlocale)
{
var cultureInfo = new CultureInfo(locale);
if (!string.IsNullOrWhiteSpace(dtlocale))
{
var datetimeCulture = CultureInfo.GetCultureInfo(dtlocale);
cultureInfo.DateTimeFormat = datetimeCulture.DateTimeFormat;
}
var viewModel = new LocaleSample
{
ShortDateSample = DateTime.Now.ToString(cultureInfo.DateTimeFormat.ShortDatePattern, cultureInfo.DateTimeFormat),
ShortDateTimeSample = DateTime.Now.ToString("G", cultureInfo.DateTimeFormat),
CurrencySample = 13.45M.ToString("C", cultureInfo),
NumberSample = 123456.ToString("N", cultureInfo),
DecimalSample = 123456.78M.ToString("N2", cultureInfo)
};
return PartialView("_LocaleSample", viewModel);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public async Task<IActionResult> ImportOpenIDConfiguration(string configUrl)
{
if (!string.IsNullOrWhiteSpace(configUrl))
{
try
{
var httpClient = new HttpClient();
var openIdConfig = await httpClient.GetFromJsonAsync<OpenIDProviderConfig>(configUrl);
return Json(openIdConfig);
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve OpenID Provider Config: {ex.Message}");
}
}
return Json(new OpenIDProviderConfig());
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[Route("/setup")]
public IActionResult Setup()
{
var installedLocales = CultureInfo.GetCultures(CultureTypes.AllCultures).Select(x=>x.Name).ToList();
installedLocales.RemoveAll(x => string.IsNullOrWhiteSpace(x));
installedLocales.Insert(0, "");
var viewModel = new ServerSettingsViewModel
{
LocaleOverride = _config.GetLocaleOverride(),
LocaleDateTimeOverride = _config.GetLocaleDateTimeOverride(),
AvailableLocales = installedLocales,
PostgresConnection = _config.GetServerPostgresConnection(),
AllowedFileExtensions = _config.GetAllowedFileUploadExtensions(),
CustomLogoURL = _config.GetLogoUrl(),
CustomSmallLogoURL = _config.GetSmallLogoUrl(),
MessageOfTheDay = _config.GetMOTD(),
WebHookURL = _config.GetWebHookUrl(),
CustomWidgetsEnabled = _config.GetCustomWidgetsEnabled(),
InvariantAPIEnabled = _config.GetInvariantApi(),
SMTPConfig = _config.GetMailConfig(),
Domain = _config.GetServerDomain(),
OIDCConfig = _config.GetOpenIDConfig(),
OpenRegistration = _config.GetServerOpenRegistration(),
DisableRegistration = _config.GetServerDisabledRegistration(),
ReminderUrgencyConfig = _config.GetReminderUrgencyConfig(),
EnableAuth = _config.GetServerAuthEnabled(),
DefaultReminderEmail = _config.GetDefaultReminderEmail(),
EnableRootUserOIDC = _config.GetEnableRootUserOIDC(),
CookieLifeSpan = _config.GetAuthCookieLifeSpan().ToString(),
KestrelAppConfig = _config.GetKestrelAppConfig()
};
return View(viewModel);
}
[HttpPost]
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult WriteServerConfiguration(ServerConfig serverConfig)
{
var result = _config.SaveServerConfig(serverConfig);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult SendTestEmail(string emailAddress, MailConfig mailConfig)
{
var result = _mailHelper.SendTestEmail(emailAddress, mailConfig);
return Json(result);
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{

View File

@ -1,11 +1,11 @@
using CarCareTracker.Helper;
using CarCareTracker.Logic;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.Text.Json;
namespace CarCareTracker.Controllers
@ -13,19 +13,410 @@ namespace CarCareTracker.Controllers
public class LoginController : Controller
{
private IDataProtector _dataProtector;
private ILoginLogic _loginLogic;
private IConfigHelper _config;
private readonly ILogger<LoginController> _logger;
public LoginController(
ILogger<LoginController> logger,
IDataProtectionProvider securityProvider
)
IDataProtectionProvider securityProvider,
ILoginLogic loginLogic,
IConfigHelper config
)
{
_dataProtector = securityProvider.CreateProtector("login");
_logger = logger;
_loginLogic = loginLogic;
_config = config;
}
public IActionResult Index()
public IActionResult Index(string redirectURL = "")
{
var remoteAuthConfig = _config.GetOpenIDConfig();
if (remoteAuthConfig.DisableRegularLogin && !string.IsNullOrWhiteSpace(remoteAuthConfig.LogOutURL))
{
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
if (remoteAuthConfig.ValidateState)
{
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (remoteAuthConfig.UsePKCE)
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Redirect(remoteAuthURL);
}
return View(model: redirectURL);
}
public IActionResult Registration(string token = "", string email = "")
{
if (_config.GetServerDisabledRegistration())
{
return RedirectToAction("Index");
}
var viewModel = new LoginModel
{
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
};
return View(viewModel);
}
public IActionResult ForgotPassword()
{
return View();
}
public IActionResult ResetPassword(string token = "", string email = "")
{
var viewModel = new LoginModel
{
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
};
return View(viewModel);
}
public IActionResult GetRemoteLoginLink()
{
var remoteAuthConfig = _config.GetOpenIDConfig();
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
if (remoteAuthConfig.ValidateState)
{
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (remoteAuthConfig.UsePKCE)
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Json(remoteAuthURL);
}
public async Task<IActionResult> RemoteAuth(string code, string state = "")
{
try
{
if (!string.IsNullOrWhiteSpace(code))
{
//received code from OIDC provider
//create http client to retrieve user token from OIDC
var httpClient = new HttpClient();
var openIdConfig = _config.GetOpenIDConfig();
//check if validate state is enabled.
if (openIdConfig.ValidateState)
{
var storedStateValue = Request.Cookies["OIDC_STATE"];
if (!string.IsNullOrWhiteSpace(storedStateValue))
{
Response.Cookies.Delete("OIDC_STATE");
}
if (string.IsNullOrWhiteSpace(storedStateValue) || string.IsNullOrWhiteSpace(state) || storedStateValue != state)
{
_logger.LogInformation("Failed OIDC State Validation - Try disabling state validation if you are confident this is not a malicious attempt.");
return new RedirectResult("/Login");
}
}
var httpParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_id", openIdConfig.ClientId),
new KeyValuePair<string, string>("client_secret", openIdConfig.ClientSecret),
new KeyValuePair<string, string>("redirect_uri", openIdConfig.RedirectURL)
};
if (openIdConfig.UsePKCE)
{
//retrieve stored challenge verifier
var storedVerifier = Request.Cookies["OIDC_VERIFIER"];
if (!string.IsNullOrWhiteSpace(storedVerifier))
{
httpParams.Add(new KeyValuePair<string, string>("code_verifier", storedVerifier));
Response.Cookies.Delete("OIDC_VERIFIER");
}
}
var httpRequest = new HttpRequestMessage(HttpMethod.Post, openIdConfig.TokenURL)
{
Content = new FormUrlEncodedContent(httpParams)
};
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
var decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
var userJwt = decodedToken?.id_token ?? string.Empty;
var userAccessToken = decodedToken?.access_token ?? string.Empty;
var tokenParser = new JsonWebTokenHandler();
bool passedSignatureCheck = true;
string signatureValidationError = "check jwks endpoint";
if (!string.IsNullOrWhiteSpace(openIdConfig.JwksURL))
{
//validate token signature if jwks endpoint is provided
var jwksData = await httpClient.GetStringAsync(openIdConfig.JwksURL);
if (!string.IsNullOrWhiteSpace(jwksData))
{
var signingKeys = new JsonWebKeySet(jwksData).GetSigningKeys();
var tokenValidationParams = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
RequireAudience = false,
IssuerSigningKeys = signingKeys,
ValidateIssuerSigningKey = true
};
var validatedIdToken = await tokenParser.ValidateTokenAsync(userJwt, tokenValidationParams);
if (!validatedIdToken.IsValid)
{
passedSignatureCheck = false;
if (validatedIdToken.Exception != null && !string.IsNullOrWhiteSpace(validatedIdToken.Exception.Message))
{
signatureValidationError = validatedIdToken.Exception.Message;
}
}
}
}
if (passedSignatureCheck)
{
if (!string.IsNullOrWhiteSpace(userJwt))
{
//validate JWT token
var parsedToken = tokenParser.ReadJsonWebToken(userJwt);
var userEmailAddress = string.Empty;
if (parsedToken.Claims.Any(x => x.Type == "email"))
{
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
}
else if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
{
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
{
userEmailAddress = userInfo?.email ?? string.Empty;
}
else
{
_logger.LogError($"OpenID Provider did not provide an email claim via UserInfo endpoint");
}
}
else
{
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
_logger.LogError($"OpenID Provider did not provide an email claim, claims returned: {string.Join(",", returnedClaims)}");
}
if (!string.IsNullOrWhiteSpace(userEmailAddress))
{
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
if (userData.Id != default)
{
AuthCookie authCookie = new AuthCookie
{
UserData = userData,
ExpiresOn = DateTime.Now.AddDays(1)
};
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append(StaticHelper.LoginCookieName, encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
return new RedirectResult("/Home");
}
else
{
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
return View("OpenIDRegistration", model: userEmailAddress);
}
}
else
{
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
}
}
else
{
_logger.LogInformation("OpenID Provider did not provide a valid id_token");
if (!string.IsNullOrWhiteSpace(tokenResult))
{
//if something was returned from the IdP but it's invalid, we want to log it as an error.
_logger.LogError($"Expected id_token, received {tokenResult}");
}
}
}
else
{
_logger.LogError($"OpenID Provider did not provide a valid id_token: {signatureValidationError}");
}
}
else
{
_logger.LogInformation("OpenID Provider did not provide a code.");
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new RedirectResult("/Login");
}
return new RedirectResult("/Login");
}
public async Task<IActionResult> RemoteAuthDebug(string code, string state = "")
{
List<OperationResponse> results = new List<OperationResponse>();
try
{
if (!string.IsNullOrWhiteSpace(code))
{
results.Add(OperationResponse.Succeed($"Received code from OpenID Provider: {code}"));
//received code from OIDC provider
//create http client to retrieve user token from OIDC
var httpClient = new HttpClient();
var openIdConfig = _config.GetOpenIDConfig();
//check if validate state is enabled.
if (openIdConfig.ValidateState)
{
var storedStateValue = Request.Cookies["OIDC_STATE"];
if (!string.IsNullOrWhiteSpace(storedStateValue))
{
Response.Cookies.Delete("OIDC_STATE");
}
if (string.IsNullOrWhiteSpace(storedStateValue) || string.IsNullOrWhiteSpace(state) || storedStateValue != state)
{
results.Add(OperationResponse.Failed($"Failed State Validation - Expected: {storedStateValue} Received: {state}"));
}
else
{
results.Add(OperationResponse.Succeed($"Passed State Validation - Expected: {storedStateValue} Received: {state}"));
}
}
var httpParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_id", openIdConfig.ClientId),
new KeyValuePair<string, string>("client_secret", openIdConfig.ClientSecret),
new KeyValuePair<string, string>("redirect_uri", openIdConfig.RedirectURL)
};
if (openIdConfig.UsePKCE)
{
//retrieve stored challenge verifier
var storedVerifier = Request.Cookies["OIDC_VERIFIER"];
if (!string.IsNullOrWhiteSpace(storedVerifier))
{
httpParams.Add(new KeyValuePair<string, string>("code_verifier", storedVerifier));
Response.Cookies.Delete("OIDC_VERIFIER");
}
}
var httpRequest = new HttpRequestMessage(HttpMethod.Post, openIdConfig.TokenURL)
{
Content = new FormUrlEncodedContent(httpParams)
};
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
var decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
var userJwt = decodedToken?.id_token ?? string.Empty;
var userAccessToken = decodedToken?.access_token ?? string.Empty;
var tokenParser = new JsonWebTokenHandler();
bool passedSignatureCheck = true;
if (!string.IsNullOrWhiteSpace(openIdConfig.JwksURL))
{
//validate token signature if jwks endpoint is provided
var jwksData = await httpClient.GetStringAsync(openIdConfig.JwksURL);
if (!string.IsNullOrWhiteSpace(jwksData))
{
var signingKeys = new JsonWebKeySet(jwksData).GetSigningKeys();
var tokenValidationParams = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
RequireAudience = false,
IssuerSigningKeys = signingKeys,
ValidateIssuerSigningKey = true
};
var validatedIdToken = await tokenParser.ValidateTokenAsync(userJwt, tokenValidationParams);
if (!validatedIdToken.IsValid)
{
passedSignatureCheck = false;
if (validatedIdToken.Exception != null && !string.IsNullOrWhiteSpace(validatedIdToken.Exception.Message))
{
results.Add(OperationResponse.Failed($"Failed JWT Validation: {validatedIdToken.Exception.Message}"));
}
} else
{
results.Add(OperationResponse.Succeed($"Passed JWT Validation - Valid To: {validatedIdToken.SecurityToken.ValidTo}"));
}
}
}
if (passedSignatureCheck)
{
if (!string.IsNullOrWhiteSpace(userJwt))
{
results.Add(OperationResponse.Succeed($"Passed JWT Parsing - id_token: {userJwt}"));
//validate JWT token
var parsedToken = tokenParser.ReadJsonWebToken(userJwt);
var userEmailAddress = string.Empty;
if (parsedToken.Claims.Any(x => x.Type == "email"))
{
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
results.Add(OperationResponse.Succeed($"Passed Claim Validation - email"));
}
else if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
{
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
{
userEmailAddress = userInfo?.email ?? string.Empty;
results.Add(OperationResponse.Succeed($"Passed Claim Validation - Retrieved email via UserInfo endpoint"));
}
else
{
results.Add(OperationResponse.Failed($"Failed Claim Validation - Unable to retrieve email via UserInfo endpoint: {openIdConfig.UserInfoURL} using access_token: {userAccessToken} - Received {userInfoResult}"));
}
}
else
{
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
results.Add(OperationResponse.Failed($"Failed Claim Validation - Expected: email Received: {string.Join(",", returnedClaims)}"));
}
if (!string.IsNullOrWhiteSpace(userEmailAddress))
{
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
if (userData.Id != default)
{
results.Add(OperationResponse.Succeed($"Passed User Validation - Email: {userEmailAddress} Username: {userData.UserName}"));
}
else
{
results.Add(OperationResponse.Succeed($"Passed Email Validation - Email: {userEmailAddress} User not registered"));
}
}
else
{
results.Add(OperationResponse.Failed($"Failed Email Validation - No email received from OpenID Provider"));
}
}
else
{
results.Add(OperationResponse.Failed($"Failed to parse JWT - Expected: id_token Received: {tokenResult}"));
}
}
else
{
results.Add(OperationResponse.Failed("Failed JWT Validation: Check Signing Keys"));
}
}
else
{
results.Add(OperationResponse.Failed("No code received from OpenID Provider"));
}
}
catch (Exception ex)
{
results.Add(OperationResponse.Failed($"Exception: {ex.Message}"));
}
return View(results);
}
[HttpPost]
public IActionResult Login(LoginModel credentials)
{
@ -37,30 +428,18 @@ namespace CarCareTracker.Controllers
//compare it against hashed credentials
try
{
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
if (existingUserConfig is not null)
var userData = _loginLogic.ValidateUserCredentials(credentials);
if (userData.Id != default)
{
//create hashes of the login credentials.
var hashedUserName = Sha256_hash(credentials.UserName);
var hashedPassword = Sha256_hash(credentials.Password);
//compare against stored hash.
if (hashedUserName == existingUserConfig.UserNameHash &&
hashedPassword == existingUserConfig.UserPasswordHash)
AuthCookie authCookie = new AuthCookie
{
//auth success, create auth cookie
//encrypt stuff.
AuthCookie authCookie = new AuthCookie
{
Id = 1, //this is hardcoded for now
UserName = credentials.UserName,
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1)
};
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
return Json(true);
}
UserData = userData,
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? _config.GetAuthCookieLifeSpan() : 1)
};
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append(StaticHelper.LoginCookieName, encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
return Json(true);
}
}
catch (Exception ex)
@ -69,26 +448,60 @@ namespace CarCareTracker.Controllers
}
return Json(false);
}
[Authorize] //User must already be logged in to do this.
[HttpPost]
public IActionResult Register(LoginModel credentials)
{
var result = _loginLogic.RegisterNewUser(credentials);
return Json(result);
}
[HttpPost]
public IActionResult RegisterOpenIdUser(LoginModel credentials)
{
var result = _loginLogic.RegisterOpenIdUser(credentials);
if (result.Success)
{
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = credentials.EmailAddress });
if (userData.Id != default)
{
AuthCookie authCookie = new AuthCookie
{
UserData = userData,
ExpiresOn = DateTime.Now.AddDays(1)
};
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append(StaticHelper.LoginCookieName, encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
}
}
return Json(result);
}
[HttpPost]
public IActionResult SendRegistrationToken(LoginModel credentials)
{
var result = _loginLogic.SendRegistrationToken(credentials);
return Json(result);
}
[HttpPost]
public IActionResult RequestResetPassword(LoginModel credentials)
{
var result = _loginLogic.RequestResetPassword(credentials);
return Json(result);
}
[HttpPost]
public IActionResult PerformPasswordReset(LoginModel credentials)
{
var result = _loginLogic.ResetPasswordByUser(credentials);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))] //User must already be logged in as root user to do this.
[HttpPost]
public IActionResult CreateLoginCreds(LoginModel credentials)
{
try
{
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
if (existingUserConfig is not null)
{
//create hashes of the login credentials.
var hashedUserName = Sha256_hash(credentials.UserName);
var hashedPassword = Sha256_hash(credentials.Password);
//copy over settings that are off limits on the settings page.
existingUserConfig.EnableAuth = true;
existingUserConfig.UserNameHash = hashedUserName;
existingUserConfig.UserPasswordHash = hashedPassword;
}
System.IO.File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig));
return Json(true);
var result = _loginLogic.CreateRootUserCredentials(credentials);
return Json(result);
}
catch (Exception ex)
{
@ -96,25 +509,19 @@ namespace CarCareTracker.Controllers
}
return Json(false);
}
[Authorize]
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult DestroyLoginCreds()
{
try
{
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
if (existingUserConfig is not null)
{
//copy over settings that are off limits on the settings page.
existingUserConfig.EnableAuth = false;
existingUserConfig.UserNameHash = string.Empty;
existingUserConfig.UserPasswordHash = string.Empty;
}
System.IO.File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig));
var result = _loginLogic.DeleteRootUserCredentials();
//destroy any login cookies.
Response.Cookies.Delete("ACCESS_TOKEN");
return Json(true);
if (result)
{
Response.Cookies.Delete(StaticHelper.LoginCookieName);
}
return Json(result);
}
catch (Exception ex)
{
@ -126,23 +533,13 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult LogOut()
{
Response.Cookies.Delete("ACCESS_TOKEN");
return Json(true);
}
private static string Sha256_hash(string value)
{
StringBuilder Sb = new StringBuilder();
using (var hash = SHA256.Create())
Response.Cookies.Delete(StaticHelper.LoginCookieName);
var remoteAuthConfig = _config.GetOpenIDConfig();
if (remoteAuthConfig.DisableRegularLogin && !string.IsNullOrWhiteSpace(remoteAuthConfig.LogOutURL))
{
Encoding enc = Encoding.UTF8;
byte[] result = hash.ComputeHash(enc.GetBytes(value));
foreach (byte b in result)
Sb.Append(b.ToString("x2"));
return Json(remoteAuthConfig.LogOutURL);
}
return Sb.ToString();
return Json("/Login");
}
}
}

View File

@ -0,0 +1,868 @@
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using System.IO.Compression;
using JsonSerializer=System.Text.Json.JsonSerializer;
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, 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 SCHEMA IF NOT EXISTS app",
"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)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.userhouseholdrecords (parentUserId INT, childUserId INT, data jsonb not null, PRIMARY KEY(parentUserId, childUserId))"
};
foreach(string cmd in cmds)
{
using (var ctext = conn.CreateCommand(cmd))
{
ctext.ExecuteNonQuery();
}
}
}
public IActionResult Export()
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(OperationResponse.Failed("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>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
var userhouseholdrecords = new List<UserHousehold>();
#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 = 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(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(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(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(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(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(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(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(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(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(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(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(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(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);
};
}
cmd = $"SELECT data FROM app.inspectionrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
inspectionrecords.Add(JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string));
}
}
foreach (var record in inspectionrecords)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecord>("inspectionrecords");
table.Upsert(record);
}
;
}
cmd = $"SELECT data FROM app.inspectionrecordtemplates";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
inspectionrecordtemplates.Add(JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string));
}
}
foreach (var record in inspectionrecordtemplates)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecordInput>("inspectionrecordtemplates");
table.Upsert(record);
};
}
cmd = $"SELECT data FROM app.userhouseholdrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
UserHousehold result = JsonSerializer.Deserialize<UserHousehold>(reader["data"] as string);
userhouseholdrecords.Add(result);
}
}
foreach (var record in userhouseholdrecords)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<UserHousehold>("userhouseholdrecords");
table.Upsert(record);
}
;
}
#endregion
var destFilePath = $"{fullFolderPath}.zip";
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
return Json(OperationResponse.Succeed($"/{tempFolder}.zip"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(OperationResponse.Failed());
}
}
public IActionResult Import(string fileName)
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(OperationResponse.Failed("Postgres connection not set up"));
}
var fullFileName = _fileHelper.GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFileName))
{
return Json(OperationResponse.Failed());
}
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>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
var userhouseholdrecords = new List<UserHousehold>();
#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", 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", 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", 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", 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", 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", 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", 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", 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", 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", 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", 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", 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", 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", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecord>("inspectionrecords");
inspectionrecords = table.FindAll().ToList();
};
foreach (var record in inspectionrecords)
{
string cmd = $"INSERT INTO app.inspectionrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.inspectionrecords_id_seq', (SELECT MAX(id) from app.inspectionrecords));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecordInput>("inspectionrecordtemplates");
inspectionrecordtemplates = table.FindAll().ToList();
};
foreach (var record in inspectionrecordtemplates)
{
string cmd = $"INSERT INTO app.inspectionrecordtemplates (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.inspectionrecordtemplates_id_seq', (SELECT MAX(id) from app.inspectionrecordtemplates));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<UserHousehold>("userhouseholdrecords");
userhouseholdrecords = table.FindAll().ToList();
}
;
foreach (var record in userhouseholdrecords)
{
string cmd = $"INSERT INTO app.userhouseholdrecords (parentUserId, childUserId, data) VALUES(@parentUserId, @childUserId, CAST(@data AS jsonb))";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("parentUserId", record.Id.ParentUserId);
ctext.Parameters.AddWithValue("childUserId", record.Id.ChildUserId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
#endregion
return Json(OperationResponse.Succeed("Data Imported Successfully"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(OperationResponse.Failed());
}
}
}
}

View File

@ -0,0 +1,216 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
{
var result = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
//check if the user uses MPG or Liters per 100km.
var userConfig = _config.GetUserConfig(User);
bool useMPG = userConfig.UseMPG;
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
bool useUKMPG = !vehicleData.IsElectric && userConfig.UseUKMPG; //do not apply UK conversion on electric vehicles.
var computedResults = _gasHelper.GetGasRecordViewModels(result, useMPG, useUKMPG);
if (userConfig.UseDescending)
{
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
}
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordViewModelContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecords = computedResults
};
return PartialView("Gas/_Gas", viewModel);
}
[HttpPost]
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), gasRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var convertedRecord = gasRecord.ToGasRecord();
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(convertedRecord, gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(gasRecord.Date),
VehicleId = gasRecord.VehicleId,
Mileage = gasRecord.Mileage,
Notes = $"Auto Insert From Gas Record. {gasRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.GasRecord, convertedRecord.Id, $"Gas Record - {gasRecord.Mileage.ToString()}")
});
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetAddGasRecordPartialView(int vehicleId)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
return PartialView("Gas/_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields } });
}
[HttpGet]
public IActionResult GetGasRecordForEditById(int gasRecordId)
{
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
var convertedResult = new GasRecordInput
{
Id = result.Id,
Mileage = result.Mileage,
VehicleId = result.VehicleId,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Files = result.Files,
Gallons = result.Gallons,
IsFillToFull = result.IsFillToFull,
MissedFuelUp = result.MissedFuelUp,
Notes = result.Notes,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields)
};
var vehicleData = _dataAccess.GetVehicleById(convertedResult.VehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordInputContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecord = convertedResult
};
return PartialView("Gas/_GasModal", viewModel);
}
private OperationResponse DeleteGasRecordWithChecks(int gasRecordId)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteGasRecordById(int gasRecordId)
{
var result = DeleteGasRecordWithChecks(gasRecordId);
return Json(result);
}
[HttpPost]
public IActionResult SaveUserGasTabPreferences(string gasUnit, string fuelMileageUnit)
{
var currentConfig = _config.GetUserConfig(User);
currentConfig.PreferredGasUnit = gasUnit;
currentConfig.PreferredGasMileageUnit = fuelMileageUnit;
var result = _config.SaveUserConfig(User, currentConfig);
return Json(result);
}
[HttpPost]
public IActionResult GetGasRecordsEditModal(List<int> recordIds)
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields;
return PartialView("Gas/_GasRecordsModal", new GasRecordEditModel { RecordIds = recordIds, EditRecord = new GasRecord { ExtraFields = extraFields } });
}
[HttpPost]
public IActionResult SaveMultipleGasRecords(GasRecordEditModel editModel)
{
var dateIsEdited = editModel.EditRecord.Date != default;
var mileageIsEdited = editModel.EditRecord.Mileage != default;
var consumptionIsEdited = editModel.EditRecord.Gallons != default;
var costIsEdited = editModel.EditRecord.Cost != default;
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
var tagsIsEdited = editModel.EditRecord.Tags.Any();
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
//handle clear overrides
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
{
editModel.EditRecord.Tags = new List<string>();
}
if (noteIsEdited && editModel.EditRecord.Notes == "---")
{
editModel.EditRecord.Notes = "";
}
bool result = false;
foreach (int recordId in editModel.RecordIds)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (dateIsEdited)
{
existingRecord.Date = editModel.EditRecord.Date;
}
if (consumptionIsEdited)
{
existingRecord.Gallons = editModel.EditRecord.Gallons;
}
if (costIsEdited)
{
existingRecord.Cost = editModel.EditRecord.Cost;
}
if (mileageIsEdited)
{
existingRecord.Mileage = editModel.EditRecord.Mileage;
}
if (noteIsEdited)
{
existingRecord.Notes = editModel.EditRecord.Notes;
}
if (tagsIsEdited)
{
existingRecord.Tags = editModel.EditRecord.Tags;
}
if (extraFieldIsEdited)
{
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
{
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField);
}
else
{
existingRecord.ExtraFields.Add(extraField);
}
}
}
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
}
}

View File

@ -0,0 +1,670 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.MapProfile;
using CarCareTracker.Models;
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[HttpGet]
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
{
return PartialView("_BulkDataImporter", mode);
}
[HttpGet]
public IActionResult GenerateCsvSample(ImportMode mode)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
switch (mode)
{
case ImportMode.ServiceRecord:
case ImportMode.RepairRecord:
case ImportMode.UpgradeRecord:
{
var exportData = new List<GenericRecordExportModel> { new GenericRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Odometer = 12345.ToString(),
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.GasRecord:
{
var exportData = new List<GasRecordExportModel> { new GasRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Odometer = 12345.ToString(),
FuelConsumed = 12.34M.ToString(),
Cost = 45.67M.ToString("C"),
IsFillToFull = true.ToString(),
MissedFuelUp = false.ToString(),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGasRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.OdometerRecord:
{
var exportData = new List<OdometerRecordExportModel> { new OdometerRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
InitialOdometer = 12345.ToString(),
Odometer = 12345.ToString(),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.TaxRecord:
{
var exportData = new List<TaxRecordExportModel> { new TaxRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.SupplyRecord:
{
var exportData = new List<SupplyRecordExportModel> { new SupplyRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
PartNumber = "TEST-123456",
PartSupplier = "Test Supplier",
PartQuantity = 1.5M.ToString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.PlanRecord:
{
var exportData = new List<PlanRecordExportModel> { new PlanRecordExportModel
{
DateCreated = DateTime.Now.ToString(),
DateModified = DateTime.Now.ToString(),
Description = "Test",
Type = ImportMode.RepairRecord.ToString(),
Priority = PlanPriority.Normal.ToString(),
Progress = PlanProgress.Testing.ToString(),
Cost = 123.45M.ToString("C"),
Notes = "Test Note"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WritePlanRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
default:
return Json(OperationResponse.Failed("No parameters"));
}
try
{
var fileBytes = _fileHelper.GetFileBytes(fullExportFilePath, true);
if (fileBytes.Length > 0)
{
return File(fileBytes, "text/csv", $"{mode.ToString().ToLower()}sample.csv");
}
else
{
return Json(OperationResponse.Failed("An error has occurred while generating CSV sample: file has zero bytes"));
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(OperationResponse.Failed($"An error has occurred while generating CSV sample: {ex.Message}"));
}
}
[TypeFilter(typeof(CollaboratorFilter), Arguments = new object[] { false, true, HouseholdPermission.View })]
[HttpGet]
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
{
if (vehicleId == default && mode != ImportMode.SupplyRecord)
{
return Json(false);
}
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
if (mode == ImportMode.ServiceRecord)
{
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
writer.Dispose();
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.RepairRecord)
{
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.UpgradeRecord)
{
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.OdometerRecord)
{
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel
{
Date = x.Date.ToShortDateString(),
Notes = x.Notes,
InitialOdometer = x.InitialMileage.ToString(),
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.SupplyRecord)
{
var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
PartNumber = x.PartNumber,
PartQuantity = x.Quantity.ToString(),
PartSupplier = x.PartSupplier,
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.TaxRecord)
{
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.PlanRecord)
{
var vehicleRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new PlanRecordExportModel
{
DateCreated = x.DateCreated.ToString("G"),
DateModified = x.DateModified.ToString("G"),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Type = x.ImportMode.ToString(),
Priority = x.Priority.ToString(),
Progress = x.Progress.ToString(),
Notes = x.Notes,
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WritePlanRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.GasRecord)
{
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = !vehicleData.IsElectric && _config.GetUserConfig(User).UseUKMPG; //do not apply UK conversion on electric vehicles.
var convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG);
var exportData = convertedRecords.Select(x => new GasRecordExportModel
{
Date = x.Date.ToString(),
Cost = x.Cost.ToString(),
FuelConsumed = x.Gallons.ToString(),
FuelEconomy = x.MilesPerGallon.ToString(),
Odometer = x.Mileage.ToString(),
IsFillToFull = x.IsFillToFull.ToString(),
MissedFuelUp = x.MissedFuelUp.ToString(),
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGasRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
return Json(OperationResponse.Failed(StaticHelper.GenericErrorMessage));
}
[TypeFilter(typeof(CollaboratorFilter), Arguments = new object[] { false, true, HouseholdPermission.Edit })]
[HttpPost]
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
{
if (vehicleId == default && mode != ImportMode.SupplyRecord)
{
return Json(false);
}
if (string.IsNullOrWhiteSpace(fileName))
{
return Json(false);
}
var fullFileName = _fileHelper.GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFileName))
{
return Json(false);
}
try
{
using (var reader = new StreamReader(fullFileName))
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
config.MissingFieldFound = null;
config.HeaderValidated = null;
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ImportMapper>();
var records = csv.GetRecords<ImportModel>().ToList();
if (records.Any())
{
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x => x.IsRequired).Select(y => y.Name);
foreach (ImportModel importModel in records)
{
var parsedDate = DateTime.Now.Date;
if (!string.IsNullOrWhiteSpace(importModel.Date))
{
parsedDate = DateTime.Parse(importModel.Date);
}
else if (!string.IsNullOrWhiteSpace(importModel.Day) && !string.IsNullOrWhiteSpace(importModel.Month) && !string.IsNullOrWhiteSpace(importModel.Year))
{
parsedDate = new DateTime(int.Parse(importModel.Year), int.Parse(importModel.Month), int.Parse(importModel.Day));
}
if (mode == ImportMode.GasRecord)
{
//convert to gas model.
var convertedRecord = new GasRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
if (string.IsNullOrWhiteSpace(importModel.Cost) && !string.IsNullOrWhiteSpace(importModel.Price))
{
//cost was not given but price is.
//fuelly sometimes exports CSVs without total cost.
var parsedPrice = decimal.Parse(importModel.Price, NumberStyles.Any);
convertedRecord.Cost = convertedRecord.Gallons * parsedPrice;
}
else
{
convertedRecord.Cost = decimal.Parse(importModel.Cost, NumberStyles.Any);
}
if (string.IsNullOrWhiteSpace(importModel.IsFillToFull) && !string.IsNullOrWhiteSpace(importModel.PartialFuelUp))
{
var parsedBool = importModel.PartialFuelUp.Trim() == "1";
convertedRecord.IsFillToFull = !parsedBool;
}
else if (!string.IsNullOrWhiteSpace(importModel.IsFillToFull))
{
var possibleFillToFullValues = new List<string> { "1", "true", "full" };
var parsedBool = possibleFillToFullValues.Contains(importModel.IsFillToFull.Trim().ToLower());
convertedRecord.IsFillToFull = parsedBool;
}
if (!string.IsNullOrWhiteSpace(importModel.MissedFuelUp))
{
var possibleMissedFuelUpValues = new List<string> { "1", "true" };
var parsedBool = possibleMissedFuelUpValues.Contains(importModel.MissedFuelUp.Trim().ToLower());
convertedRecord.MissedFuelUp = parsedBool;
}
//insert record into db, check to make sure fuelconsumed is not zero so we don't get a divide by zero error.
if (convertedRecord.Gallons > 0)
{
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Gas Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.GasRecord, convertedRecord.Id, $"Gas Record - {convertedRecord.Mileage.ToString()}")
});
}
}
}
else if (mode == ImportMode.ServiceRecord)
{
var convertedRecord = new ServiceRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Service Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.ServiceRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
else if (mode == ImportMode.OdometerRecord)
{
var convertedRecord = new OdometerRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.PlanRecord)
{
var progressIsEnum = Enum.TryParse(importModel.Progress, out PlanProgress parsedProgress);
var typeIsEnum = Enum.TryParse(importModel.Type, out ImportMode parsedType);
var priorityIsEnum = Enum.TryParse(importModel.Priority, out PlanPriority parsedPriority);
var convertedRecord = new PlanRecord()
{
VehicleId = vehicleId,
DateCreated = DateTime.Parse(importModel.DateCreated),
DateModified = DateTime.Parse(importModel.DateModified),
Progress = parsedProgress,
ImportMode = parsedType,
Priority = parsedPriority,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Plan Record on {importModel.DateCreated}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_planRecordDataAccess.SavePlanRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.RepairRecord)
{
var convertedRecord = new CollisionRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Repair Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.RepairRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
else if (mode == ImportMode.UpgradeRecord)
{
var convertedRecord = new UpgradeRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.UpgradeRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
else if (mode == ImportMode.SupplyRecord)
{
var convertedRecord = new SupplyRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
PartNumber = importModel.PartNumber,
PartSupplier = importModel.PartSupplier,
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
Description = importModel.Description,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Notes = importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.TaxRecord)
{
var convertedRecord = new TaxRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(convertedRecord);
}
}
}
}
}
return Json(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Occurred While Bulk Inserting");
return Json(false);
}
}
}
}

View File

@ -0,0 +1,270 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplatesByVehicleId(vehicleId);
return PartialView("Inspection/_InspectionRecordTemplateSelector", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetInspectionRecordsByVehicleId(int vehicleId)
{
var result = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("Inspection/_InspectionRecords", result);
}
[HttpGet]
public IActionResult GetAddInspectionRecordTemplatePartialView()
{
return PartialView("Inspection/_InspectionRecordTemplateEditModal", new InspectionRecordInput());
}
[HttpGet]
public IActionResult GetEditInspectionRecordTemplatePartialView(int inspectionRecordTemplateId)
{
var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
if (existingRecord.ReminderRecordId.Any())
{
bool reminderMissing = false;
//check if reminder still exists and is still recurring.
foreach (int reminderRecordId in existingRecord.ReminderRecordId)
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
reminderMissing = true;
break;
}
}
if (reminderMissing)
{
//clear out reminders attached to record.
existingRecord.ReminderRecordId = new List<int>();
}
}
return PartialView("Inspection/_InspectionRecordTemplateEditModal", existingRecord);
}
[HttpGet]
public IActionResult GetAddInspectionRecordFieldPartialView()
{
return PartialView("Inspection/_InspectionRecordField", new InspectionRecordTemplateField());
}
public IActionResult GetAddInspectionRecordFieldOptionsPartialView()
{
return PartialView("Inspection/_InspectionRecordFieldOptions", new List<InspectionRecordTemplateFieldOption>());
}
public IActionResult GetAddInspectionRecordFieldOptionPartialView()
{
return PartialView("Inspection/_InspectionRecordFieldOption", new InspectionRecordTemplateFieldOption());
}
[HttpPost]
public IActionResult SaveInspectionRecordTemplateToVehicleId(InspectionRecordInput inspectionRecordTemplate)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), inspectionRecordTemplate.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
var result = _inspectionRecordTemplateDataAccess.SaveInspectionReportTemplateToVehicle(inspectionRecordTemplate);
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
private OperationResponse DeleteInspectionRecordTemplateWithChecks(int inspectionRecordTemplateId)
{
var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _inspectionRecordTemplateDataAccess.DeleteInspectionRecordTemplateById(existingRecord.Id);
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
private OperationResponse DeleteInspectionRecordWithChecks(int inspectionRecordId)
{
var existingRecord = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _inspectionRecordDataAccess.DeleteInspectionRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(existingRecord, "inspectionrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var result = DeleteInspectionRecordTemplateWithChecks(inspectionRecordTemplateId);
return Json(result);
}
[HttpPost]
public IActionResult DeleteInspectionRecordById(int inspectionRecordId)
{
var result = DeleteInspectionRecordWithChecks(inspectionRecordId);
return Json(result);
}
[HttpGet]
public IActionResult GetAddInspectionRecordPartialView(int inspectionRecordTemplateId)
{
var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//populate date
existingRecord.Date = DateTime.Now.ToShortDateString();
if (existingRecord.ReminderRecordId.Any())
{
bool reminderMissing = false;
//check if reminder still exists and is still recurring.
foreach (int reminderRecordId in existingRecord.ReminderRecordId)
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
reminderMissing = true;
break;
}
}
if (reminderMissing)
{
//clear out reminders attached to record.
existingRecord.ReminderRecordId = new List<int>();
}
}
return PartialView("Inspection/_InspectionRecordModal", existingRecord);
}
[HttpGet]
public IActionResult GetViewInspectionRecordPartialView(int inspectionRecordId)
{
var result = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Json(OperationResponse.Failed("Access Denied"));
}
return PartialView("Inspection/_InspectionRecordViewModal", result);
}
[HttpPost]
public IActionResult SaveInspectionRecordToVehicleId(InspectionRecordInput inspectionRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), inspectionRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
inspectionRecord.Files = inspectionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
//push back any reminders
if (inspectionRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in inspectionRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(inspectionRecord.Date), inspectionRecord.Mileage);
}
}
var convertedRecord = inspectionRecord.ToInspectionRecord();
var result = _inspectionRecordDataAccess.SaveInspectionRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(convertedRecord, "inspectionrecord.add", User.Identity.Name));
}
if (convertedRecord.Id != 0)
{
//insert into service record
List<UploadedFiles> newAttachments = new List<UploadedFiles>();
newAttachments.Add(new UploadedFiles { Name = inspectionRecord.Description, Location = StaticHelper.GetRecordAttachment(ImportMode.InspectionRecord, convertedRecord.Id)});
newAttachments.AddRange(inspectionRecord.Files);
_serviceRecordDataAccess.SaveServiceRecordToVehicle(new ServiceRecord
{
Date = DateTime.Parse(inspectionRecord.Date),
VehicleId = inspectionRecord.VehicleId,
Mileage = inspectionRecord.Mileage,
Description = inspectionRecord.Description,
Cost = inspectionRecord.Cost,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = newAttachments
});
//auto-insert into odometer if configured
if (inspectionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(inspectionRecord.Date),
VehicleId = inspectionRecord.VehicleId,
Mileage = inspectionRecord.Mileage,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description)
});
}
//create action items
var inspectionFieldsWithActionItems = inspectionRecord.Fields.Where(x => x.HasActionItem);
if (inspectionFieldsWithActionItems.Any())
{
foreach (InspectionRecordTemplateField inspectionField in inspectionFieldsWithActionItems)
{
if (inspectionField.ToInspectionRecordResult().Failed)
{
_planRecordDataAccess.SavePlanRecordToVehicle(new PlanRecord
{
DateCreated = DateTime.Now,
DateModified = DateTime.Now,
VehicleId = inspectionRecord.VehicleId,
Description = inspectionField.ActionItemDescription,
ImportMode = inspectionField.ActionItemType,
Priority = inspectionField.ActionItemPriority,
Progress = PlanProgress.Backlog,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
}
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpPost]
public IActionResult UpdateInspectionRecord(InspectionRecordInput inspectionRecord)
{
var existingRecord = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecord.Id);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
existingRecord.Tags = inspectionRecord.Tags;
existingRecord.Files = inspectionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _inspectionRecordDataAccess.SaveInspectionRecordToVehicle(existingRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(existingRecord, "inspectionrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
}
}

View File

@ -0,0 +1,106 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.OrderByDescending(x => x.Pinned).ThenBy(x => x.Description).ToList();
return PartialView("Note/_Notes", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPinnedNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.Where(x => x.Pinned).ToList();
return Json(result);
}
[HttpPost]
public IActionResult SaveNoteToVehicleId(Note note)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), note.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
bool isCreate = note.Id == default; //needed here since Notes don't use an input object.
var result = _noteDataAccess.SaveNoteToVehicle(note);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(note, isCreate ? "noterecord.add" : "noterecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddNotePartialView()
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.NoteRecord).ExtraFields;
return PartialView("Note/_NoteModal", new Note() { ExtraFields = extraFields });
}
[HttpGet]
public IActionResult GetNoteForEditById(int noteId)
{
var result = _noteDataAccess.GetNoteById(noteId);
result.ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.NoteRecord).ExtraFields);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
return PartialView("Note/_NoteModal", result);
}
private OperationResponse DeleteNoteWithChecks(int noteId)
{
var existingRecord = _noteDataAccess.GetNoteById(noteId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(existingRecord, "noterecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteNoteById(int noteId)
{
var result = DeleteNoteWithChecks(noteId);
return Json(result);
}
[HttpPost]
public IActionResult PinNotes(List<int> noteIds, bool isToggle = false, bool pinStatus = false)
{
var result = false;
foreach (int noteId in noteIds)
{
var existingNote = _noteDataAccess.GetNoteById(noteId);
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingNote.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (isToggle)
{
existingNote.Pinned = !existingNote.Pinned;
}
else
{
existingNote.Pinned = pinStatus;
}
result = _noteDataAccess.SaveNoteToVehicle(existingNote);
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
}
}

View File

@ -0,0 +1,186 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[HttpPost]
public IActionResult ForceRecalculateDistanceByVehicleId(int vehicleId)
{
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), vehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
result = _odometerLogic.AutoConvertOdometerRecord(result);
return Json(OperationResponse.Conditional(result.Any(), string.Empty, StaticHelper.GenericErrorMessage));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetOdometerRecordsByVehicleId(int vehicleId)
{
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
//determine if conversion is needed.
if (result.All(x => x.InitialMileage == default))
{
result = _odometerLogic.AutoConvertOdometerRecord(result);
}
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("Odometer/_OdometerRecords", result);
}
[HttpPost]
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), odometerRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var convertedRecord = odometerRecord.ToOdometerRecord();
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(convertedRecord, odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
{
return PartialView("Odometer/_OdometerRecordModal", new OdometerRecordInput() { InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()), ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
}
[HttpPost]
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds)
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields;
return PartialView("Odometer/_OdometerRecordsModal", new OdometerRecordEditModel { RecordIds = recordIds, EditRecord = new OdometerRecord { ExtraFields = extraFields } });
}
[HttpPost]
public IActionResult SaveMultipleOdometerRecords(OdometerRecordEditModel editModel)
{
var dateIsEdited = editModel.EditRecord.Date != default;
var initialMileageIsEdited = editModel.EditRecord.InitialMileage != default;
var mileageIsEdited = editModel.EditRecord.Mileage != default;
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
var tagsIsEdited = editModel.EditRecord.Tags.Any();
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
//handle clear overrides
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
{
editModel.EditRecord.Tags = new List<string>();
}
if (noteIsEdited && editModel.EditRecord.Notes == "---")
{
editModel.EditRecord.Notes = "";
}
bool result = false;
foreach (int recordId in editModel.RecordIds)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (dateIsEdited)
{
existingRecord.Date = editModel.EditRecord.Date;
}
if (initialMileageIsEdited)
{
existingRecord.InitialMileage = editModel.EditRecord.InitialMileage;
}
if (mileageIsEdited)
{
existingRecord.Mileage = editModel.EditRecord.Mileage;
}
if (noteIsEdited)
{
existingRecord.Notes = editModel.EditRecord.Notes;
}
if (tagsIsEdited)
{
existingRecord.Tags = editModel.EditRecord.Tags;
}
if (extraFieldIsEdited)
{
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
{
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField);
}
else
{
existingRecord.ExtraFields.Add(extraField);
}
}
}
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
{
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new OdometerRecordInput
{
Id = result.Id,
Date = result.Date.ToShortDateString(),
InitialMileage = result.InitialMileage,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields)
};
return PartialView("Odometer/_OdometerRecordModal", convertedResult);
}
private OperationResponse DeleteOdometerRecordWithChecks(int odometerRecordId)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
{
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,334 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPlanRecordsByVehicleId(int vehicleId)
{
var result = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
return PartialView("Plan/_PlanRecords", result);
}
[HttpPost]
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//populate createdDate
if (planRecord.Id == default)
{
planRecord.DateCreated = DateTime.Now.ToString("G");
}
planRecord.DateModified = DateTime.Now.ToString("G");
//move files from temp.
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (planRecord.Supplies.Any())
{
planRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description));
if (planRecord.CopySuppliesAttachment)
{
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
}
}
if (planRecord.DeletedRequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord.ToPlanRecord(), planRecord.Id == default ? "planrecord.add" : "planrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpPost]
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//check if template name already taken.
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
if (planRecord.Id == default && existingRecord)
{
return Json(OperationResponse.Failed("A template with that description already exists for this vehicle"));
}
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _planRecordTemplateDataAccess.SavePlanRecordTemplateToVehicle(planRecord);
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPlanRecordTemplatesForVehicleId(int vehicleId)
{
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicleId);
return PartialView("Plan/_PlanRecordTemplateModal", result);
}
[HttpPost]
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(OperationResponse.Failed(StaticHelper.GenericErrorMessage));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return Json(OperationResponse.Failed("Access Denied"));
}
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult OrderPlanSupplies(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(OperationResponse.Failed("Unable to find template"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.View))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (existingRecord.Supplies.Any())
{
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
return PartialView("Plan/_PlanOrderSupplies", suppliesToOrder);
}
else
{
return Json(OperationResponse.Failed("Template has No Supplies"));
}
}
[HttpPost]
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(OperationResponse.Failed("Unable to find template"));
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (existingRecord.Supplies.Any())
{
//check if all supplies are available
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
if (supplyAvailability.Any(x => x.Missing))
{
return Json(OperationResponse.Failed("Missing Supplies, Please Delete This Template and Recreate It."));
}
else if (supplyAvailability.Any(x => x.Insufficient))
{
return Json(OperationResponse.Failed("Insufficient Supplies"));
}
}
if (existingRecord.ReminderRecordId != default)
{
//check if reminder still exists and is still recurring.
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
return Json(OperationResponse.Failed("Missing or Non-recurring Reminder, Please Delete This Template and Recreate It."));
}
}
//populate createdDate
existingRecord.DateCreated = DateTime.Now.ToString("G");
existingRecord.DateModified = DateTime.Now.ToString("G");
existingRecord.Id = default;
if (existingRecord.Supplies.Any())
{
existingRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(existingRecord.Supplies, DateTime.Parse(existingRecord.DateCreated), existingRecord.Description);
if (existingRecord.CopySuppliesAttachment)
{
existingRecord.Files.AddRange(GetSuppliesAttachments(existingRecord.Supplies));
}
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
return Json(OperationResponse.Conditional(result, "Plan Record Added", StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddPlanRecordPartialView()
{
return PartialView("Plan/_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult GetAddPlanRecordPartialView(PlanRecordInput? planModel)
{
if (planModel is not null)
{
planModel.ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields;
return PartialView("Plan/_PlanRecordModal", planModel);
}
return PartialView("Plan/_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
{
if (planRecordId == default)
{
return Json(OperationResponse.Failed(StaticHelper.GenericErrorMessage));
}
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
existingRecord.Progress = planProgress;
existingRecord.DateModified = DateTime.Now;
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
if (planProgress == PlanProgress.Done)
{
int newRecordId = 0;
//convert plan record to service/upgrade/repair record.
if (existingRecord.ImportMode == ImportMode.ServiceRecord)
{
var newRecord = new ServiceRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
else if (existingRecord.ImportMode == ImportMode.RepairRecord)
{
var newRecord = new CollisionRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
else if (existingRecord.ImportMode == ImportMode.UpgradeRecord)
{
var newRecord = new UpgradeRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
if (newRecordId != default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Now.Date,
VehicleId = existingRecord.VehicleId,
Mileage = odometer,
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
ExtraFields = existingRecord.ExtraFields,
Files = StaticHelper.CreateAttachmentFromRecord(existingRecord.ImportMode, newRecordId, existingRecord.Description)
});
}
//push back any reminders
if (existingRecord.ReminderRecordId != default)
{
PushbackRecurringReminderRecordWithChecks(existingRecord.ReminderRecordId, DateTime.Now, odometer);
}
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetPlanRecordTemplateForEditById(int planRecordTemplateId)
{
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
return PartialView("Plan/_PlanRecordTemplateEditModal", result);
}
[HttpGet]
public IActionResult GetPlanRecordForEditById(int planRecordId)
{
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new PlanRecordInput
{
Id = result.Id,
Description = result.Description,
DateCreated = result.DateCreated.ToString("G"),
DateModified = result.DateModified.ToString("G"),
ImportMode = result.ImportMode,
Priority = result.Priority,
Progress = result.Progress,
Cost = result.Cost,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
RequisitionHistory = result.RequisitionHistory,
ReminderRecordId = result.ReminderRecordId,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields)
};
return PartialView("Plan/_PlanRecordModal", convertedResult);
}
[HttpPost]
public IActionResult DeletePlanRecordById(int planRecordId)
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//restore any requisitioned supplies if it has not been converted to other record types.
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
{
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(existingRecord, "planrecord.delete", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
}
}

View File

@ -0,0 +1,193 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId, DateTime dateCompare)
{
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
List<ReminderRecordViewModel> results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, dateCompare);
return results;
}
private bool GetAndUpdateVehicleUrgentOrPastDueReminders(int vehicleId)
{
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
//check if user wants auto-refresh past-due reminders
if (_config.GetUserConfig(User).EnableAutoReminderRefresh && _userLogic.UserCanEditVehicle(GetUserID(), vehicleId, HouseholdPermission.Edit))
{
//check for past due reminders that are eligible for recurring.
var pastDueAndRecurring = result.Where(x => x.Urgency == ReminderUrgency.PastDue && x.IsRecurring);
if (pastDueAndRecurring.Any())
{
foreach (ReminderRecordViewModel reminderRecord in pastDueAndRecurring)
{
//update based on recurring intervals.
//pull reminderRecord based on ID
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecord.Id);
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, null, null);
//save to db.
_reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
//set urgency to not urgent so it gets excluded in count.
reminderRecord.Urgency = ReminderUrgency.NotUrgent;
}
}
}
//check for very urgent or past due reminders that were not eligible for recurring.
var pastDueAndUrgentReminders = result.Where(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
if (pastDueAndUrgentReminders.Any())
{
return true;
}
return false;
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId)
{
var result = GetAndUpdateVehicleUrgentOrPastDueReminders(vehicleId);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetReminderRecordsByVehicleId(int vehicleId)
{
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
result = result.OrderByDescending(x => x.Urgency).ToList();
return PartialView("Reminder/_ReminderRecords", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
{
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
result.RemoveAll(x => !x.IsRecurring);
result = result.OrderByDescending(x => x.Urgency).ThenBy(x => x.Description).ToList();
return PartialView("_RecurringReminderSelector", result);
}
[HttpPost]
public IActionResult PushbackRecurringReminderRecord(int reminderRecordId)
{
var result = PushbackRecurringReminderRecordWithChecks(reminderRecordId, null, null);
return Json(result);
}
private OperationResponse PushbackRecurringReminderRecordWithChecks(int reminderRecordId, DateTime? currentDate, int? currentMileage)
{
try
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is not null && existingReminder.Id != default && existingReminder.IsRecurring)
{
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingReminder.VehicleId, HouseholdPermission.Edit))
{
return OperationResponse.Failed("Access Denied");
}
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, currentDate, currentMileage);
//save to db.
var reminderUpdateResult = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
if (!reminderUpdateResult)
{
_logger.LogError("Unable to update reminder either because the reminder no longer exists or is no longer recurring");
return OperationResponse.Failed("Unable to update reminder either because the reminder no longer exists or is no longer recurring");
}
return OperationResponse.Succeed();
}
else
{
_logger.LogError("Unable to update reminder because it no longer exists.");
return OperationResponse.Failed("Unable to update reminder because it no longer exists.");
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return OperationResponse.Failed(StaticHelper.GenericErrorMessage);
}
}
[HttpPost]
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), reminderRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord.ToReminderRecord(), reminderRecord.Id == default ? "reminderrecord.add" : "reminderrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpPost]
public IActionResult GetAddReminderRecordPartialView(ReminderRecordInput? reminderModel)
{
if (reminderModel is not null)
{
return PartialView("Reminder/_ReminderRecordModal", reminderModel);
}
else
{
return PartialView("Reminder/_ReminderRecordModal", new ReminderRecordInput());
}
}
[HttpGet]
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
{
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new ReminderRecordInput
{
Id = result.Id,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Notes = result.Notes,
VehicleId = result.VehicleId,
Mileage = result.Mileage,
Metric = result.Metric,
IsRecurring = result.IsRecurring,
FixedIntervals = result.FixedIntervals,
UseCustomThresholds = result.UseCustomThresholds,
CustomThresholds = result.CustomThresholds,
ReminderMileageInterval = result.ReminderMileageInterval,
ReminderMonthInterval = result.ReminderMonthInterval,
CustomMileageInterval = result.CustomMileageInterval,
CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Tags = result.Tags
};
return PartialView("Reminder/_ReminderRecordModal", convertedResult);
}
private OperationResponse DeleteReminderRecordWithChecks(int reminderRecordId)
{
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteReminderRecordById(int reminderRecordId)
{
var result = DeleteReminderRecordWithChecks(reminderRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,133 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCollisionRecordsByVehicleId(int vehicleId)
{
var result = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("Collision/_CollisionRecords", result);
}
[HttpPost]
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), collisionRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (collisionRecord.Supplies.Any())
{
collisionRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description));
if (collisionRecord.CopySuppliesAttachment)
{
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
}
}
if (collisionRecord.DeletedRequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
}
//push back any reminders
if (collisionRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in collisionRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(collisionRecord.Date), collisionRecord.Mileage);
}
}
var convertedRecord = collisionRecord.ToCollisionRecord();
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(collisionRecord.Date),
VehicleId = collisionRecord.VehicleId,
Mileage = collisionRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {collisionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.RepairRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddCollisionRecordPartialView()
{
return PartialView("Collision/_CollisionRecordModal", new CollisionRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
{
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new CollisionRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields)
};
return PartialView("Collision/_CollisionRecordModal", convertedResult);
}
private OperationResponse DeleteCollisionRecordWithChecks(int collisionRecordId)
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
{
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,788 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Text.Json;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetReportPartialView(int vehicleId)
{
//get records
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
var serviceRecords = vehicleRecords.ServiceRecords;
var gasRecords = vehicleRecords.GasRecords;
var collisionRecords = vehicleRecords.CollisionRecords;
var taxRecords = vehicleRecords.TaxRecords;
var upgradeRecords = vehicleRecords.UpgradeRecords;
var odometerRecords = vehicleRecords.OdometerRecords;
var userConfig = _config.GetUserConfig(User);
var viewModel = new ReportViewModel() { ReportHeaderForVehicle = new ReportHeader() };
//check if vehicle map exists
viewModel.HasVehicleImageMap = !string.IsNullOrWhiteSpace(vehicleData.MapLocation);
//check if custom widgets are configured
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
//get totalCostMakeUp
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
//get costbymonth
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, 0));
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, 0));
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, 0));
viewModel.CostForVehicleByMonth = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled)
}).ToList();
//set available metrics
var visibleTabs = userConfig.VisibleTabs;
if (visibleTabs.Contains(ImportMode.OdometerRecord) || odometerRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.OdometerRecord);
}
if (visibleTabs.Contains(ImportMode.ServiceRecord) || serviceRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.ServiceRecord);
}
if (visibleTabs.Contains(ImportMode.RepairRecord) || collisionRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.RepairRecord);
}
if (visibleTabs.Contains(ImportMode.UpgradeRecord) || upgradeRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.UpgradeRecord);
}
if (visibleTabs.Contains(ImportMode.GasRecord) || gasRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.GasRecord);
}
if (visibleTabs.Contains(ImportMode.TaxRecord) || taxRecords.Any())
{
viewModel.AvailableMetrics.Add(ImportMode.TaxRecord);
}
//get reminders
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
viewModel.ReminderMakeUpForVehicle = new ReminderMakeUpForVehicle
{
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
};
//populate year dropdown.
var numbersArray = new List<int>();
if (serviceRecords.Any())
{
numbersArray.Add(serviceRecords.Min(x => x.Date.Year));
}
if (collisionRecords.Any())
{
numbersArray.Add(collisionRecords.Min(x => x.Date.Year));
}
if (gasRecords.Any())
{
numbersArray.Add(gasRecords.Min(x => x.Date.Year));
}
if (upgradeRecords.Any())
{
numbersArray.Add(upgradeRecords.Min(x => x.Date.Year));
}
if (odometerRecords.Any())
{
numbersArray.Add(odometerRecords.Min(x => x.Date.Year));
}
var minYear = numbersArray.Any() ? numbersArray.Min() : DateTime.Now.AddYears(-5).Year;
var yearDifference = DateTime.Now.Year - minYear + 1;
for (int i = 0; i < yearDifference; i++)
{
viewModel.Years.Add(DateTime.Now.AddYears(i * -1).Year);
}
//get collaborators
var collaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId);
var userCanModify = _userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleId);
viewModel.Collaborators = new VehicleCollaboratorViewModel { CanModifyCollaborators = userCanModify, Collaborators = collaborators};
//get MPG per month.
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, !vehicleData.IsElectric && userConfig.UseUKMPG);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
var averageMPG = _gasHelper.GetAverageGasMileage(mileageData, userConfig.UseMPG);
mileageData.RemoveAll(x => x.MilesPerGallon == default);
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
}).ToList();
if (invertedFuelMileageUnit)
{
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
{
if (monthMileage.Cost != default)
{
monthMileage.Cost = 100 / monthMileage.Cost;
}
}
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
if (newAverageMPG != 0)
{
newAverageMPG = 100 / newAverageMPG;
}
averageMPG = newAverageMPG.ToString("F");
}
var mpgViewModel = new MPGForVehicleByMonth {
CostData = monthlyMileageData,
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
};
viewModel.FuelMileageForVehicleByMonth = mpgViewModel;
//report header
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
viewModel.ReportHeaderForVehicle.TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
viewModel.ReportHeaderForVehicle.AverageMPG = $"{averageMPG} {mpgViewModel.Unit}";
viewModel.ReportHeaderForVehicle.MaxOdometer = maxMileage;
viewModel.ReportHeaderForVehicle.DistanceTraveled = maxMileage - minMileage;
return PartialView("_Report", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCollaboratorsForVehicle(int vehicleId)
{
var result = _userLogic.GetCollaboratorsForVehicle(vehicleId);
var userCanModify = _userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleId);
var viewModel = new VehicleCollaboratorViewModel
{
Collaborators = result,
CanModifyCollaborators = userCanModify
};
return PartialView("_Collaborators", viewModel);
}
[TypeFilter(typeof(StrictCollaboratorFilter), Arguments = new object[] {false, true})]
[HttpPost]
public IActionResult AddCollaboratorsToVehicle(int vehicleId, string username)
{
var result = _userLogic.AddCollaboratorToVehicle(vehicleId, username);
return Json(result);
}
[TypeFilter(typeof(StrictCollaboratorFilter), Arguments = new object[] { false, true })]
[HttpPost]
public IActionResult DeleteCollaboratorFromVehicle(int userId, int vehicleId)
{
var result = _userLogic.DeleteCollaboratorFromVehicle(userId, vehicleId);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetSummaryForVehicle(int vehicleId, int year = 0)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
var serviceRecords = vehicleRecords.ServiceRecords;
var gasRecords = vehicleRecords.GasRecords;
var collisionRecords = vehicleRecords.CollisionRecords;
var taxRecords = vehicleRecords.TaxRecords;
var upgradeRecords = vehicleRecords.UpgradeRecords;
var odometerRecords = vehicleRecords.OdometerRecords;
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
gasRecords.RemoveAll(x => x.Date.Year != year);
collisionRecords.RemoveAll(x => x.Date.Year != year);
taxRecords.RemoveAll(x => x.Date.Year != year);
upgradeRecords.RemoveAll(x => x.Date.Year != year);
odometerRecords.RemoveAll(x => x.Date.Year != year);
}
var userConfig = _config.GetUserConfig(User);
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, !vehicleData.IsElectric && userConfig.UseUKMPG);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
var averageMPG = _gasHelper.GetAverageGasMileage(mileageData, userConfig.UseMPG);
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
if (invertedFuelMileageUnit)
{
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
if (newAverageMPG != 0)
{
newAverageMPG = 100 / newAverageMPG;
}
averageMPG = newAverageMPG.ToString("F");
}
var mpgUnit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit;
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var viewModel = new ReportHeader()
{
TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords),
AverageMPG = $"{averageMPG} {mpgUnit}",
MaxOdometer = maxMileage,
DistanceTraveled = maxMileage - minMileage
};
return PartialView("_ReportHeader", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0)
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
gasRecords.RemoveAll(x => x.Date.Year != year);
collisionRecords.RemoveAll(x => x.Date.Year != year);
taxRecords.RemoveAll(x => x.Date.Year != year);
upgradeRecords.RemoveAll(x => x.Date.Year != year);
}
var viewModel = new CostMakeUpForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
return PartialView("_CostMakeUpReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCostTableForVehicle(int vehicleId, int year = 0)
{
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
var serviceRecords = vehicleRecords.ServiceRecords;
var gasRecords = vehicleRecords.GasRecords;
var collisionRecords = vehicleRecords.CollisionRecords;
var taxRecords = vehicleRecords.TaxRecords;
var upgradeRecords = vehicleRecords.UpgradeRecords;
var odometerRecords = vehicleRecords.OdometerRecords;
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
gasRecords.RemoveAll(x => x.Date.Year != year);
collisionRecords.RemoveAll(x => x.Date.Year != year);
taxRecords.RemoveAll(x => x.Date.Year != year);
upgradeRecords.RemoveAll(x => x.Date.Year != year);
odometerRecords.RemoveAll(x => x.Date.Year != year);
}
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var totalDistanceTraveled = maxMileage - minMileage;
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, year, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
var viewModel = new CostTableForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost),
TotalDistance = totalDistanceTraveled,
DistanceUnit = vehicleData.UseHours ? "Cost Per Hour" : userConfig.UseMPG ? "Cost Per Mile" : "Cost Per Kilometer",
NumberOfDays = totalDays
};
return PartialView("_CostTableReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetVehicleImageMap(int vehicleId)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
VehicleImageMap imageMap = new VehicleImageMap();
if (!string.IsNullOrWhiteSpace(vehicleData.MapLocation))
{
var fullFilePath = _fileHelper.GetFullFilePath(vehicleData.MapLocation);
if (!string.IsNullOrWhiteSpace(fullFilePath))
{
var fullFileText = _fileHelper.GetFileText(fullFilePath);
imageMap = JsonSerializer.Deserialize<VehicleImageMap>(fullFileText) ?? new VehicleImageMap();
}
}
return PartialView("_VehicleImageMap", imageMap);
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetReminderMakeUpByVehicle(int vehicleId, int daysToAdd)
{
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now.AddDays(daysToAdd));
var viewModel = new ReminderMakeUpForVehicle
{
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
};
return PartialView("_ReminderMakeUpReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetVehicleAttachments(int vehicleId, List<ImportMode> exportTabs)
{
List<GenericReportModel> attachmentData = new List<GenericReportModel>();
if (exportTabs.Contains(ImportMode.ServiceRecord))
{
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.ServiceRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.RepairRecord))
{
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.RepairRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.UpgradeRecord))
{
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.UpgradeRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.GasRecord))
{
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.GasRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.TaxRecord))
{
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.TaxRecord,
Date = x.Date,
Odometer = 0,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.OdometerRecord))
{
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.OdometerRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.NoteRecord))
{
var records = _noteDataAccess.GetNotesByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.NoteRecord,
Date = DateTime.Now,
Odometer = 0,
Files = x.Files
}));
}
if (attachmentData.Any())
{
attachmentData = attachmentData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
if (string.IsNullOrWhiteSpace(result))
{
return Json(OperationResponse.Failed());
}
return Json(OperationResponse.Succeed(result));
}
else
{
return Json(OperationResponse.Failed("No Attachments Found"));
}
}
public IActionResult GetReportParameters()
{
var viewModel = new ReportParameter() {
VisibleColumns = new List<string> {
nameof(GenericReportModel.DataType),
nameof(GenericReportModel.Date),
nameof(GenericReportModel.Odometer),
nameof(GenericReportModel.Description),
nameof(GenericReportModel.Cost),
nameof(GenericReportModel.Notes)
}
};
//get all extra fields from service records, repairs, upgrades, and tax records.
var recordTypes = new List<int>() { 0, 1, 3, 4 };
var extraFields = new List<string>();
foreach(int recordType in recordTypes)
{
extraFields.AddRange(_extraFieldDataAccess.GetExtraFieldsById(recordType).ExtraFields.Select(x => x.Name));
}
viewModel.ExtraFields = extraFields.Distinct().ToList();
return PartialView("_ReportParameters", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetVehicleHistory(int vehicleId, ReportParameter reportParameter)
{
var vehicleHistory = new VehicleHistoryViewModel();
vehicleHistory.ReportParameters = reportParameter;
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = !vehicleHistory.VehicleData.IsElectric && _config.GetUserConfig(User).UseUKMPG;
var gasViewModels = _gasHelper.GetGasRecordViewModels(vehicleRecords.GasRecords, useMPG, useUKMPG);
//filter by tags
if (reportParameter.Tags.Any())
{
if (reportParameter.TagFilter == TagFilter.Exclude)
{
vehicleRecords.OdometerRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.ServiceRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.CollisionRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.TaxRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
gasViewModels.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.GasRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
}
else if (reportParameter.TagFilter == TagFilter.IncludeOnly)
{
vehicleRecords.OdometerRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.ServiceRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.CollisionRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.UpgradeRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.TaxRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
gasViewModels.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.GasRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
}
}
//filter by date range.
if (reportParameter.FilterByDateRange && !string.IsNullOrWhiteSpace(reportParameter.StartDate) && !string.IsNullOrWhiteSpace(reportParameter.EndDate))
{
var startDate = DateTime.Parse(reportParameter.StartDate).Date;
var endDate = DateTime.Parse(reportParameter.EndDate).Date;
//validate date range
if (endDate >= startDate) //allow for same day.
{
vehicleHistory.StartDate = reportParameter.StartDate;
vehicleHistory.EndDate = reportParameter.EndDate;
//remove all records with dates after the end date and dates before the start date.
vehicleRecords.OdometerRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.ServiceRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.CollisionRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.TaxRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
gasViewModels.RemoveAll(x => DateTime.Parse(x.Date).Date > endDate || DateTime.Parse(x.Date).Date < startDate);
vehicleRecords.GasRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
}
}
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
vehicleHistory.Odometer = maxMileage.ToString("N0");
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var distanceTraveled = maxMileage - minMileage;
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
{
var endDate = vehicleHistory.VehicleData.SoldDate;
int daysOwned = 0;
if (string.IsNullOrWhiteSpace(endDate))
{
endDate = DateTime.Now.ToShortDateString();
}
try
{
daysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days;
vehicleHistory.DaysOwned = daysOwned.ToString("N0");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
vehicleHistory.DaysOwned = string.Empty;
}
//calculate depreciation
var totalDepreciation = vehicleHistory.VehicleData.PurchasePrice - vehicleHistory.VehicleData.SoldPrice;
//we only calculate depreciation if a sold price is provided.
if (totalDepreciation != default && vehicleHistory.VehicleData.SoldPrice != default)
{
vehicleHistory.TotalDepreciation = totalDepreciation;
if (daysOwned != default)
{
vehicleHistory.DepreciationPerDay = Math.Abs(totalDepreciation / daysOwned);
}
if (distanceTraveled != default)
{
vehicleHistory.DepreciationPerMile = Math.Abs(totalDepreciation / distanceTraveled);
}
}
}
List<GenericReportModel> reportData = new List<GenericReportModel>();
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
vehicleHistory.TotalGasCost = gasViewModels.Sum(x => x.Cost);
vehicleHistory.TotalCost = vehicleRecords.ServiceRecords.Sum(x => x.Cost) + vehicleRecords.CollisionRecords.Sum(x => x.Cost) + vehicleRecords.UpgradeRecords.Sum(x => x.Cost) + vehicleRecords.TaxRecords.Sum(x => x.Cost);
if (distanceTraveled != default)
{
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
vehicleHistory.TotalCostPerMile = vehicleHistory.TotalCost / distanceTraveled;
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
}
var averageMPG = "0";
if (gasViewModels.Any())
{
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
}
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleHistory.VehicleData.IsElectric, vehicleHistory.VehicleData.UseHours, useMPG, useUKMPG);
if (fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l")
{
//conversion needed.
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
if (newAverageMPG != 0)
{
newAverageMPG = 100 / newAverageMPG;
}
averageMPG = newAverageMPG.ToString("F");
fuelEconomyMileageUnit = preferredFuelMileageUnit;
}
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
//insert servicerecords
reportData.AddRange(vehicleRecords.ServiceRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.ServiceRecord,
ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
}));
//repair records
reportData.AddRange(vehicleRecords.CollisionRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.RepairRecord,
ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
}));
reportData.AddRange(vehicleRecords.UpgradeRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.UpgradeRecord,
ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
}));
reportData.AddRange(vehicleRecords.TaxRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = 0,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.TaxRecord,
ExtraFields = x.ExtraFields
}));
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
return PartialView("_VehicleHistory", vehicleHistory);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetMonthMPGByVehicle(int vehicleId, int year = 0)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var userConfig = _config.GetUserConfig(User);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, !vehicleData.IsElectric && userConfig.UseUKMPG);
if (year != 0)
{
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
}
mileageData.RemoveAll(x => x.MilesPerGallon == default);
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
}).ToList();
if (invertedFuelMileageUnit)
{
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
{
if (monthMileage.Cost != default)
{
monthMileage.Cost = 100 / monthMileage.Cost;
}
}
}
var mpgViewModel = new MPGForVehicleByMonth
{
CostData = monthlyMileageData,
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
};
return PartialView("_MPGByMonthReport", mpgViewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetCostByMonthByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year));
}
if (selectedMetrics.Contains(ImportMode.RepairRecord))
{
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year));
}
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
{
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year));
}
if (selectedMetrics.Contains(ImportMode.GasRecord))
{
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year));
}
if (selectedMetrics.Contains(ImportMode.TaxRecord))
{
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled)
}).ToList();
return PartialView("_GasCostByMonthReport", groupedRecord);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetCostByMonthAndYearByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.RepairRecord))
{
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
{
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.GasRecord))
{
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.TaxRecord))
{
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year, true));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId, x.Year }).OrderByDescending(x=>x.Key.Year).Select(x => new CostForVehicleByMonth
{
Year = x.Key.Year,
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled),
MonthId = x.Key.MonthId
}).ToList();
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var viewModel = new CostDistanceTableForVehicle { CostData = groupedRecord };
viewModel.DistanceUnit = vehicleData.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
return PartialView("_CostDistanceTableReport", viewModel);
}
[HttpGet]
public IActionResult GetAdditionalWidgets()
{
var widgets = _fileHelper.GetWidgets();
return PartialView("_ReportWidgets", widgets);
}
}
}

View File

@ -0,0 +1,133 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetServiceRecordsByVehicleId(int vehicleId)
{
var result = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("Service/_ServiceRecords", result);
}
[HttpPost]
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), serviceRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (serviceRecord.Supplies.Any())
{
serviceRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description));
if (serviceRecord.CopySuppliesAttachment)
{
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
}
}
if (serviceRecord.DeletedRequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
}
//push back any reminders
if (serviceRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in serviceRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(serviceRecord.Date), serviceRecord.Mileage);
}
}
var convertedRecord = serviceRecord.ToServiceRecord();
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(serviceRecord.Date),
VehicleId = serviceRecord.VehicleId,
Mileage = serviceRecord.Mileage,
Notes = $"Auto Insert From Service Record: {serviceRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.ServiceRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddServiceRecordPartialView()
{
return PartialView("Service/_ServiceRecordModal", new ServiceRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
{
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new ServiceRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields)
};
return PartialView("Service/_ServiceRecordModal", convertedResult);
}
private OperationResponse DeleteServiceRecordWithChecks(int serviceRecordId)
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "servicerecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteServiceRecordById(int serviceRecordId)
{
var result = DeleteServiceRecordWithChecks(serviceRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,243 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
private List<SupplyAvailability> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
{
//returns empty string if all supplies are available
var result = new List<SupplyAvailability>();
foreach (SupplyUsage supply in supplyUsage)
{
//get supply record.
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
if (supplyData == null)
{
result.Add(new SupplyAvailability { Missing = true });
}
else
{
result.Add(new SupplyAvailability { Missing = false, Description = supplyData.Description, Required = supply.Quantity, InStock = supplyData.Quantity });
}
}
return result;
}
private List<UploadedFiles> GetSuppliesAttachments(List<SupplyUsage> supplyUsage)
{
List<UploadedFiles> results = new List<UploadedFiles>();
foreach (SupplyUsage supply in supplyUsage)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
results.AddRange(result.Files);
}
return results;
}
private List<SupplyUsageHistory> RequisitionSupplyRecordsByUsage(List<SupplyUsage> supplyUsage, DateTime dateRequisitioned, string usageDescription)
{
List<SupplyUsageHistory> results = new List<SupplyUsageHistory>();
foreach (SupplyUsage supply in supplyUsage)
{
//get supply record.
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
var unitCost = (result.Quantity != 0) ? result.Cost / result.Quantity : 0;
//deduct quantity used.
result.Quantity -= supply.Quantity;
//deduct cost.
result.Cost -= (supply.Quantity * unitCost);
//check decimal places to ensure that it always has a max of 3 decimal places.
var roundedDecimal = decimal.Round(result.Cost, 3);
if (roundedDecimal != result.Cost)
{
//Too many decimals
result.Cost = roundedDecimal;
}
//create new requisitionrrecord
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.SupplyId,
Date = dateRequisitioned,
Description = usageDescription,
Quantity = supply.Quantity,
Cost = (supply.Quantity * unitCost)
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
requisitionRecord.Description = result.Description; //change the name of the description for plan/service/repair/upgrade records
requisitionRecord.PartNumber = result.PartNumber; //populate part number if not displayed in supplies modal.
results.Add(requisitionRecord);
}
return results;
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
return PartialView("Supply/_SupplyRecords", result);
}
[HttpGet]
public IActionResult GetSupplyRecordsForPlanRecordTemplate(int planRecordTemplateId)
{
var viewModel = new SupplyUsageViewModel();
var planRecordTemplate = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (planRecordTemplate != default && planRecordTemplate.VehicleId != default)
{
var supplies = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(planRecordTemplate.VehicleId);
if (_config.GetServerEnableShopSupplies())
{
supplies.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
}
supplies.RemoveAll(x => x.Quantity <= 0);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
supplies = supplies.OrderByDescending(x => x.Date).ToList();
}
else
{
supplies = supplies.OrderBy(x => x.Date).ToList();
}
viewModel.Supplies = supplies;
viewModel.Usage = planRecordTemplate.Supplies;
}
return PartialView("Supply/_SupplyUsage", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
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)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
var viewModel = new SupplyUsageViewModel
{
Supplies = result
};
return PartialView("Supply/_SupplyUsage", viewModel);
}
[HttpPost]
public IActionResult SaveSupplyRecordToVehicleId(SupplyRecordInput supplyRecord)
{
if (supplyRecord.VehicleId != default)
{
//security check only if not editing shop supply.
if (!_userLogic.UserCanEditVehicle(GetUserID(), supplyRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
}
else if (!_config.GetServerEnableShopSupplies())
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(supplyRecord.ToSupplyRecord(), supplyRecord.Id == default ? "supplyrecord.add" : "supplyrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddSupplyRecordPartialView()
{
return PartialView("Supply/_SupplyRecordModal", new SupplyRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (result.VehicleId != default)
{
//security check only if not editing shop supply.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
}
else if (!_config.GetServerEnableShopSupplies())
{
return Redirect("/Error/Unauthorized");
}
if (result.RequisitionHistory.Any())
{
//requisition history when viewed through the supply is always immutable.
result.RequisitionHistory = result.RequisitionHistory.Select(x => new SupplyUsageHistory { Id = default, Cost = x.Cost, Description = x.Description, Date = x.Date, PartNumber = x.PartNumber, Quantity = x.Quantity }).ToList();
}
//convert to Input object.
var convertedResult = new SupplyRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
PartNumber = result.PartNumber,
Quantity = result.Quantity,
PartSupplier = result.PartSupplier,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields)
};
return PartialView("Supply/_SupplyRecordModal", convertedResult);
}
private OperationResponse DeleteSupplyRecordWithChecks(int supplyRecordId)
{
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (existingRecord.VehicleId != default)
{
//security check only if not editing shop supply.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
}
else if (!_config.GetServerEnableShopSupplies())
{
return OperationResponse.Failed("Access Denied");
}
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(existingRecord, "supplyrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,122 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetTaxRecordsByVehicleId(int vehicleId)
{
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
return PartialView("Tax/_TaxRecords", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult CheckRecurringTaxRecords(int vehicleId)
{
try
{
var result = _vehicleLogic.UpdateRecurringTaxes(vehicleId);
return Json(result);
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(false);
}
}
[HttpPost]
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), taxRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
//push back any reminders
if (taxRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in taxRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(taxRecord.Date), null);
}
}
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
_vehicleLogic.UpdateRecurringTaxes(taxRecord.VehicleId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord.ToTaxRecord(), taxRecord.Id == default ? "taxrecord.add" : "taxrecord.update", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddTaxRecordPartialView()
{
return PartialView("Tax/_TaxRecordModal", new TaxRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetTaxRecordForEditById(int taxRecordId)
{
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new TaxRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Notes = result.Notes,
VehicleId = result.VehicleId,
IsRecurring = result.IsRecurring,
RecurringInterval = result.RecurringInterval,
CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
};
return PartialView("Tax/_TaxRecordModal", convertedResult);
}
private OperationResponse DeleteTaxRecordWithChecks(int taxRecordId)
{
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteTaxRecordById(int taxRecordId)
{
var result = DeleteTaxRecordWithChecks(taxRecordId);
return Json(result);
}
}
}

View File

@ -0,0 +1,133 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetUpgradeRecordsByVehicleId(int vehicleId)
{
var result = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("Upgrade/_UpgradeRecords", result);
}
[HttpPost]
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), upgradeRecord.VehicleId, HouseholdPermission.Edit))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//move files from temp.
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (upgradeRecord.Supplies.Any())
{
upgradeRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description));
if (upgradeRecord.CopySuppliesAttachment)
{
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
}
}
if (upgradeRecord.DeletedRequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
}
//push back any reminders
if (upgradeRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in upgradeRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Mileage);
}
}
var convertedRecord = upgradeRecord.ToUpgradeRecord();
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
}
if (convertedRecord != default && upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(upgradeRecord.Date),
VehicleId = upgradeRecord.VehicleId,
Mileage = upgradeRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {upgradeRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.UpgradeRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage));
}
[HttpGet]
public IActionResult GetAddUpgradeRecordPartialView()
{
return PartialView("Upgrade/_UpgradeRecordModal", new UpgradeRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
{
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId, HouseholdPermission.View))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object.
var convertedResult = new UpgradeRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields)
};
return PartialView("Upgrade/_UpgradeRecordModal", convertedResult);
}
private OperationResponse DeleteUpgradeRecordWithChecks(int upgradeRecordId)
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId, HouseholdPermission.Delete))
{
return OperationResponse.Failed("Access Denied");
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.delete", User.Identity.Name));
}
return OperationResponse.Conditional(result, string.Empty, StaticHelper.GenericErrorMessage);
}
[HttpPost]
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
{
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
return Json(result);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,13 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o out
ARG TARGETARCH
RUN dotnet restore -a $TARGETARCH
RUN dotnet publish -a $TARGETARCH -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
EXPOSE 8080
CMD ["./CarCareTracker"]
CMD ["./CarCareTracker"]

9
Enum/DashboardMetric.cs Normal file
View File

@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum DashboardMetric
{
Default = 0,
TotalCost = 1,
CostPerMile = 2
}
}

12
Enum/ExtraFieldType.cs Normal file
View File

@ -0,0 +1,12 @@
namespace CarCareTracker.Models
{
public enum ExtraFieldType
{
Text = 0,
Number = 1,
Decimal = 2,
Date = 3,
Time = 4,
Location = 5
}
}

View File

@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum HouseholdPermission
{
View = 0,
Edit = 1,
Delete = 2
}
}

View File

@ -5,6 +5,15 @@
ServiceRecord = 0,
RepairRecord = 1,
GasRecord = 2,
TaxRecord = 3
TaxRecord = 3,
UpgradeRecord = 4,
ReminderRecord = 5,
NoteRecord = 6,
SupplyRecord = 7,
Dashboard = 8,
PlanRecord = 9,
OdometerRecord = 10,
VehicleRecord = 11,
InspectionRecord = 12
}
}

View File

@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum InspectionFieldType
{
Text = 0,
Check = 1,
Radio = 2
}
}

10
Enum/KioskMode.cs Normal file
View File

@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum KioskMode
{
Vehicle = 0,
Plan = 1,
Reminder = 2,
Cycle = 3
}
}

9
Enum/PlanPriority.cs Normal file
View File

@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum PlanPriority
{
Critical = 0,
Normal = 1,
Low = 2
}
}

10
Enum/PlanProgress.cs Normal file
View File

@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum PlanProgress
{
Backlog = 0,
InProgress = 1,
Testing = 2,
Done = 3
}
}

View File

@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum ReminderIntervalUnit
{
Months = 1,
Days = 2
}
}

View File

@ -0,0 +1,24 @@
namespace CarCareTracker.Models
{
public enum ReminderMileageInterval
{
Other = 0,
FiftyMiles = 50,
OneHundredMiles = 100,
FiveHundredMiles = 500,
OneThousandMiles = 1000,
ThreeThousandMiles = 3000,
FourThousandMiles = 4000,
FiveThousandMiles = 5000,
SevenThousandFiveHundredMiles = 7500,
TenThousandMiles = 10000,
FifteenThousandMiles = 15000,
TwentyThousandMiles = 20000,
ThirtyThousandMiles = 30000,
FortyThousandMiles = 40000,
FiftyThousandMiles = 50000,
SixtyThousandMiles = 60000,
OneHundredThousandMiles = 100000,
OneHundredFiftyThousandMiles = 150000
}
}

View File

@ -0,0 +1,14 @@
namespace CarCareTracker.Models
{
public enum ReminderMonthInterval
{
Other = 0,
OneMonth = 1,
ThreeMonths = 3,
SixMonths = 6,
OneYear = 12,
TwoYears = 24,
ThreeYears = 36,
FiveYears = 60
}
}

8
Enum/TagFilter.cs Normal file
View File

@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum TagFilter
{
Exclude = 0,
IncludeOnly = 1
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class CollisionRecordDataAccess : ICollisionRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "collisionrecords";
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.Find(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
return collisionRecords.ToList() ?? new List<CollisionRecord>();
};
}
public CollisionRecord GetCollisionRecordById(int collisionRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<CollisionRecord>(tableName);
return table.FindById(collisionRecordId);
};
}
public bool DeleteCollisionRecordById(int collisionRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<CollisionRecord>(tableName);
table.Delete(collisionRecordId);
return true;
};
}
public bool SaveCollisionRecordToVehicle(CollisionRecord collisionRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<CollisionRecord>(tableName);
table.Upsert(collisionRecord);
return true;
};
}
public bool DeleteAllCollisionRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.DeleteMany(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class GasRecordDataAccess: IGasRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "gasrecords";
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.Find(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
return gasRecords.ToList() ?? new List<GasRecord>();
};
}
public GasRecord GetGasRecordById(int gasRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<GasRecord>(tableName);
return table.FindById(gasRecordId);
};
}
public bool DeleteGasRecordById(int gasRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<GasRecord>(tableName);
table.Delete(gasRecordId);
return true;
};
}
public bool SaveGasRecordToVehicle(GasRecord gasRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<GasRecord>(tableName);
table.Upsert(gasRecord);
return true;
};
}
public bool DeleteAllGasRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.DeleteMany(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class CollisionRecordDataAccess : ICollisionRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "collisionrecords";
public CollisionRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.Find(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
return collisionRecords.ToList() ?? new List<CollisionRecord>();
}
public CollisionRecord GetCollisionRecordById(int collisionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
return table.FindById(collisionRecordId);
}
public bool DeleteCollisionRecordById(int collisionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
table.Delete(collisionRecordId);
db.Checkpoint();
return true;
}
public bool SaveCollisionRecordToVehicle(CollisionRecord collisionRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
table.Upsert(collisionRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllCollisionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.DeleteMany(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,36 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
namespace CarCareTracker.External.Implementations
{
public class ExtraFieldDataAccess : IExtraFieldDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "extrafields";
public ExtraFieldDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<RecordExtraField> GetExtraFields()
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
return table.FindAll().ToList();
}
public RecordExtraField GetExtraFieldsById(int importMode)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
return table.FindById(importMode) ?? new RecordExtraField();
}
public bool SaveExtraFields(RecordExtraField record)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
table.Upsert(record);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class GasRecordDataAccess : IGasRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "gasrecords";
public GasRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.Find(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
return gasRecords.ToList() ?? new List<GasRecord>();
}
public GasRecord GetGasRecordById(int gasRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
return table.FindById(gasRecordId);
}
public bool DeleteGasRecordById(int gasRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
table.Delete(gasRecordId);
db.Checkpoint();
return true;
}
public bool SaveGasRecordToVehicle(GasRecord gasRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
table.Upsert(gasRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllGasRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.DeleteMany(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class InspectionRecordDataAccess : IInspectionRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "inspectionrecords";
public InspectionRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
var inspectionRecords = table.Find(Query.EQ(nameof(InspectionRecord.VehicleId), vehicleId));
return inspectionRecords.ToList() ?? new List<InspectionRecord>();
}
public InspectionRecord GetInspectionRecordById(int inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
return table.FindById(inspectionRecordId);
}
public bool DeleteInspectionRecordById(int inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
table.Delete(inspectionRecordId);
db.Checkpoint();
return true;
}
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
table.Upsert(inspectionRecordId);
db.Checkpoint();
return true;
}
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(InspectionRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class InspectionRecordTemplateDataAccess : IInspectionRecordTemplateDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "inspectionrecordtemplates";
public InspectionRecordTemplateDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
var inspectionRecords = table.Find(Query.EQ(nameof(InspectionRecordInput.VehicleId), vehicleId));
return inspectionRecords.ToList() ?? new List<InspectionRecordInput>();
}
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
return table.FindById(inspectionRecordTemplateId);
}
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
table.Delete(inspectionRecordTemplateId);
db.Checkpoint();
return true;
}
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
table.Upsert(inspectionRecordTemplate);
db.Checkpoint();
return true;
}
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(InspectionRecordInput.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class NoteDataAccess : INoteDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "notes";
public NoteDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<Note> GetNotesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
var noteToReturn = table.Find(Query.EQ(nameof(Note.VehicleId), vehicleId));
return noteToReturn.ToList() ?? new List<Note>();
}
public Note GetNoteById(int noteId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
return table.FindById(noteId);
}
public bool SaveNoteToVehicle(Note note)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
table.Upsert(note);
db.Checkpoint();
return true;
}
public bool DeleteNoteById(int noteId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
table.Delete(noteId);
db.Checkpoint();
return true;
}
public bool DeleteAllNotesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
var notes = table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class OdometerRecordDataAccess : IOdometerRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "odometerrecords";
public OdometerRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.Find(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
return odometerRecords.ToList() ?? new List<OdometerRecord>();
}
public OdometerRecord GetOdometerRecordById(int odometerRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
return table.FindById(odometerRecordId);
}
public bool DeleteOdometerRecordById(int odometerRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
table.Delete(odometerRecordId);
db.Checkpoint();
return true;
}
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
table.Upsert(odometerRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.DeleteMany(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class PlanRecordDataAccess : IPlanRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "planrecords";
public PlanRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.Find(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
return planRecords.ToList() ?? new List<PlanRecord>();
}
public PlanRecord GetPlanRecordById(int planRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
return table.FindById(planRecordId);
}
public bool DeletePlanRecordById(int planRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
table.Delete(planRecordId);
db.Checkpoint();
return true;
}
public bool SavePlanRecordToVehicle(PlanRecord planRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
table.Upsert(planRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class PlanRecordTemplateDataAccess : IPlanRecordTemplateDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "planrecordtemplates";
public PlanRecordTemplateDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<PlanRecordInput> GetPlanRecordTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
var planRecords = table.Find(Query.EQ(nameof(PlanRecordInput.VehicleId), vehicleId));
return planRecords.ToList() ?? new List<PlanRecordInput>();
}
public PlanRecordInput GetPlanRecordTemplateById(int planRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
return table.FindById(planRecordId);
}
public bool DeletePlanRecordTemplateById(int planRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
table.Delete(planRecordId);
db.Checkpoint();
return true;
}
public bool SavePlanRecordTemplateToVehicle(PlanRecordInput planRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
table.Upsert(planRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllPlanRecordTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecordInput.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ReminderRecordDataAccess : IReminderRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "reminderrecords";
public ReminderRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.Find(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
return reminderRecords.ToList() ?? new List<ReminderRecord>();
}
public ReminderRecord GetReminderRecordById(int reminderRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
return table.FindById(reminderRecordId);
}
public bool DeleteReminderRecordById(int reminderRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
table.Delete(reminderRecordId);
db.Checkpoint();
return true;
}
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
table.Upsert(reminderRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.DeleteMany(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ServiceRecordDataAccess : IServiceRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "servicerecords";
public ServiceRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.Find(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
return serviceRecords.ToList() ?? new List<ServiceRecord>();
}
public ServiceRecord GetServiceRecordById(int serviceRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
return table.FindById(serviceRecordId);
}
public bool DeleteServiceRecordById(int serviceRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
table.Delete(serviceRecordId);
db.Checkpoint();
return true;
}
public bool SaveServiceRecordToVehicle(ServiceRecord serviceRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
table.Upsert(serviceRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllServiceRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.DeleteMany(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class SupplyRecordDataAccess : ISupplyRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "supplyrecords";
public SupplyRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.Find(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
return supplyRecords.ToList() ?? new List<SupplyRecord>();
}
public SupplyRecord GetSupplyRecordById(int supplyRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
return table.FindById(supplyRecordId);
}
public bool DeleteSupplyRecordById(int supplyRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
table.Delete(supplyRecordId);
db.Checkpoint();
return true;
}
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
table.Upsert(supplyRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.DeleteMany(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class TaxRecordDataAccess : ITaxRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "taxrecords";
public TaxRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.Find(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
return taxRecords.ToList() ?? new List<TaxRecord>();
}
public TaxRecord GetTaxRecordById(int taxRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
return table.FindById(taxRecordId);
}
public bool DeleteTaxRecordById(int taxRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
table.Delete(taxRecordId);
db.Checkpoint();
return true;
}
public bool SaveTaxRecordToVehicle(TaxRecord taxRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
table.Upsert(taxRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllTaxRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.DeleteMany(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,53 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class TokenRecordDataAccess : ITokenRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "tokenrecords";
public TokenRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<Token> GetTokens()
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
return table.FindAll().ToList();
}
public Token GetTokenRecordByBody(string tokenBody)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.Body), tokenBody));
return tokenRecord ?? new Token();
}
public Token GetTokenRecordByEmailAddress(string emailAddress)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.EmailAddress), emailAddress));
return tokenRecord ?? new Token();
}
public bool CreateNewToken(Token token)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
table.Insert(token);
db.Checkpoint();
return true;
}
public bool DeleteToken(int tokenId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
table.Delete(tokenId);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class UpgradeRecordDataAccess : IUpgradeRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
public UpgradeRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
private static string tableName = "upgraderecords";
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.Find(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
return upgradeRecords.ToList() ?? new List<UpgradeRecord>();
}
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
return table.FindById(upgradeRecordId);
}
public bool DeleteUpgradeRecordById(int upgradeRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Delete(upgradeRecordId);
db.Checkpoint();
return true;
}
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Upsert(upgradeRecord);
db.Checkpoint();
return true;
}
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.DeleteMany(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,81 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
namespace CarCareTracker.External.Implementations
{
public class UserAccessDataAccess : IUserAccessDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "useraccessrecords";
public UserAccessDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
/// <summary>
/// Gets a list of vehicles user have access to.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public List<UserAccess> GetUserAccessByUserId(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x => x.Id.UserId == userId).ToList();
}
public UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId).FirstOrDefault();
}
public List<UserAccess> GetUserAccessByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x => x.Id.VehicleId == vehicleId).ToList();
}
public bool SaveUserAccess(UserAccess userAccess)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.Upsert(userAccess);
db.Checkpoint();
return true;
}
public bool DeleteUserAccess(int userId, int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId);
db.Checkpoint();
return true;
}
/// <summary>
/// Delete all access records when a vehicle is deleted.
/// </summary>
/// <param name="vehicleId"></param>
/// <returns></returns>
public bool DeleteAllAccessRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x => x.Id.VehicleId == vehicleId);
db.Checkpoint();
return true;
}
/// <summary>
/// Delete all access records when a user is deleted.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public bool DeleteAllAccessRecordsByUserId(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x => x.Id.UserId == userId);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,38 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
namespace CarCareTracker.External.Implementations
{
public class UserConfigDataAccess : IUserConfigDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "userconfigrecords";
public UserConfigDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public UserConfigData GetUserConfig(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
return table.FindById(userId);
}
public bool SaveUserConfig(UserConfigData userConfigData)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
table.Upsert(userConfigData);
db.Checkpoint();
return true;
}
public bool DeleteUserConfig(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
table.Delete(userId);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,76 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
namespace CarCareTracker.External.Implementations
{
public class UserHouseholdDataAccess : IUserHouseholdDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "userhouseholdrecords";
public UserHouseholdDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<UserHousehold> GetUserHouseholdByParentUserId(int parentUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
return table.Find(x => x.Id.ParentUserId == parentUserId).ToList();
}
public List<UserHousehold> GetUserHouseholdByChildUserId(int childUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
return table.Find(x => x.Id.ChildUserId == childUserId).ToList();
}
public UserHousehold GetUserHouseholdByParentAndChildUserId(int parentUserId, int childUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
return table.Find(x => x.Id.ParentUserId == parentUserId && x.Id.ChildUserId == childUserId).FirstOrDefault();
}
public bool SaveUserHousehold(UserHousehold userHousehold)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
table.Upsert(userHousehold);
db.Checkpoint();
return true;
}
public bool DeleteUserHousehold(int parentUserId, int childUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
table.DeleteMany(x => x.Id.ParentUserId == parentUserId && x.Id.ChildUserId == childUserId);
db.Checkpoint();
return true;
}
/// <summary>
/// Delete all household records when a parent user is deleted.
/// </summary>
/// <param name="parentUserId"></param>
/// <returns></returns>
public bool DeleteAllHouseholdRecordsByParentUserId(int parentUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
table.DeleteMany(x => x.Id.ParentUserId == parentUserId);
db.Checkpoint();
return true;
}
/// <summary>
/// Delete all household records when a child user is deleted.
/// </summary>
/// <param name="childUserId"></param>
/// <returns></returns>
public bool DeleteAllHouseholdRecordsByChildUserId(int childUserId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserHousehold>(tableName);
table.DeleteMany(x => x.Id.ChildUserId == childUserId);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,60 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using LiteDB;
using CarCareTracker.Helper;
namespace CarCareTracker.External.Implementations
{
public class UserRecordDataAccess : IUserRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "userrecords";
public UserRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<UserData> GetUsers()
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
return table.FindAll().ToList();
}
public UserData GetUserRecordByUserName(string userName)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindOne(Query.EQ(nameof(UserData.UserName), userName));
return userRecord ?? new UserData();
}
public UserData GetUserRecordByEmailAddress(string emailAddress)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindOne(Query.EQ(nameof(UserData.EmailAddress), emailAddress));
return userRecord ?? new UserData();
}
public UserData GetUserRecordById(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindById(userId);
return userRecord ?? new UserData();
}
public bool SaveUserRecord(UserData userRecord)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
table.Upsert(userRecord);
db.Checkpoint();
return true;
}
public bool DeleteUserRecord(int userId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
table.Delete(userId);
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,44 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
namespace CarCareTracker.External.Implementations
{
public class VehicleDataAccess : IVehicleDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "vehicles";
public VehicleDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public bool SaveVehicle(Vehicle vehicle)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
var result = table.Upsert(vehicle);
db.Checkpoint();
return true;
}
public bool DeleteVehicle(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
var result = table.Delete(vehicleId);
db.Checkpoint();
return result;
}
public List<Vehicle> GetVehicles()
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
return table.FindAll().ToList();
}
public Vehicle GetVehicleById(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
return table.FindById(vehicleId);
}
}
}

View File

@ -1,40 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class NoteDataAccess: INoteDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "notes";
public Note GetNoteByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Note>(tableName);
var noteToReturn = table.FindOne(Query.EQ(nameof(Note.VehicleId), vehicleId));
return noteToReturn ?? new Note();
};
}
public bool SaveNoteToVehicleId(Note note)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Note>(tableName);
table.Upsert(note);
return true;
};
}
public bool DeleteNoteByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Note>(tableName);
table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -0,0 +1,161 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,99 @@
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 SCHEMA IF NOT EXISTS app; 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 List<RecordExtraField> GetExtraFields()
{
try
{
string cmd = $"SELECT data FROM app.{tableName}";
var results = new List<RecordExtraField>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
RecordExtraField result = JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string);
results.Add(result);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<RecordExtraField>();
}
}
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)) ON CONFLICT(id) DO UPDATE SET data = CAST(@data AS jsonb)";
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;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGInspectionRecordDataAccess : IInspectionRecordDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGInspectionRecordDataAccess> _logger;
private static string tableName = "inspectionrecords";
public PGInspectionRecordDataAccess(IConfiguration config, ILogger<PGInspectionRecordDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (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<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
var results = new List<InspectionRecord>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecord inspectionRecord = JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string);
results.Add(inspectionRecord);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<InspectionRecord>();
}
}
public InspectionRecord GetInspectionRecordById(int inspectionRecordId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
var result = new InspectionRecord();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecord inspectionRecord = JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string);
result = inspectionRecord;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new InspectionRecord();
}
}
public bool DeleteInspectionRecordById(int inspectionRecordId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId)
{
try
{
if (inspectionRecordId.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", inspectionRecordId.VehicleId);
ctext.Parameters.AddWithValue("data", "{}");
inspectionRecordId.Id = Convert.ToInt32(ctext.ExecuteScalar());
//update json data
if (inspectionRecordId.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(inspectionRecordId);
ctextU.Parameters.AddWithValue("id", inspectionRecordId.Id);
ctextU.Parameters.AddWithValue("data", serializedData);
return ctextU.ExecuteNonQuery() > 0;
}
}
return inspectionRecordId.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(inspectionRecordId);
ctext.Parameters.AddWithValue("id", inspectionRecordId.Id);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGInspectionRecordTemplateDataAccess : IInspectionRecordTemplateDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGInspectionRecordTemplateDataAccess> _logger;
private static string tableName = "inspectionrecordtemplates";
public PGInspectionRecordTemplateDataAccess(IConfiguration config, ILogger<PGInspectionRecordTemplateDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (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<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
var results = new List<InspectionRecordInput>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecordInput inspectionRecord = JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string);
results.Add(inspectionRecord);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<InspectionRecordInput>();
}
}
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
var result = new InspectionRecordInput();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordTemplateId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecordInput inspectionRecord = JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string);
result = inspectionRecord;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new InspectionRecordInput();
}
}
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordTemplateId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate)
{
try
{
if (inspectionRecordTemplate.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", inspectionRecordTemplate.VehicleId);
ctext.Parameters.AddWithValue("data", "{}");
inspectionRecordTemplate.Id = Convert.ToInt32(ctext.ExecuteScalar());
//update json data
if (inspectionRecordTemplate.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(inspectionRecordTemplate);
ctextU.Parameters.AddWithValue("id", inspectionRecordTemplate.Id);
ctextU.Parameters.AddWithValue("data", serializedData);
return ctextU.ExecuteNonQuery() > 0;
}
}
return inspectionRecordTemplate.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(inspectionRecordTemplate);
ctext.Parameters.AddWithValue("id", inspectionRecordTemplate.Id);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,154 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View 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 SCHEMA IF NOT EXISTS app; 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;
}
}
}
}

View File

@ -0,0 +1,160 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,212 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,108 @@
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 SCHEMA IF NOT EXISTS app; 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);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,189 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGUserHouseholdDataAccess : IUserHouseholdDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGUserHouseholdDataAccess> _logger;
private static string tableName = "userhouseholdrecords";
public PGUserHouseholdDataAccess(IConfiguration config, ILogger<PGUserHouseholdDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (parentUserId INT, childUserId INT, data jsonb not null, PRIMARY KEY(parentUserId, childUserId))";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
public List<UserHousehold> GetUserHouseholdByParentUserId(int parentUserId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE parentUserId = @parentUserId";
var results = new List<UserHousehold>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("parentUserId", parentUserId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
UserHousehold result = JsonSerializer.Deserialize<UserHousehold>(reader["data"] as string);
results.Add(result);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<UserHousehold>();
}
}
public List<UserHousehold> GetUserHouseholdByChildUserId(int childUserId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE childUserId = @childUserId";
var results = new List<UserHousehold>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("childUserId", childUserId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
UserHousehold result = JsonSerializer.Deserialize<UserHousehold>(reader["data"] as string);
results.Add(result);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<UserHousehold>();
}
}
public UserHousehold GetUserHouseholdByParentAndChildUserId(int parentUserId, int childUserId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE parentUserId = @parentUserId AND childUserId = @childUserId";
UserHousehold result = null;
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("parentUserId", parentUserId);
ctext.Parameters.AddWithValue("childUserId", childUserId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
result = JsonSerializer.Deserialize<UserHousehold>(reader["data"] as string);
return result;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new UserHousehold();
}
}
public bool SaveUserHousehold(UserHousehold userHousehold)
{
try
{
string cmd = $"INSERT INTO app.{tableName} (parentUserId, childUserId, data) VALUES(@parentUserId, @childUserId, CAST(@data AS jsonb)) ON CONFLICT(parentUserId, childUserId) DO UPDATE SET data = CAST(@data AS jsonb)";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
var serializedData = JsonSerializer.Serialize(userHousehold);
ctext.Parameters.AddWithValue("parentUserId", userHousehold.Id.ParentUserId);
ctext.Parameters.AddWithValue("childUserId", userHousehold.Id.ChildUserId);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteUserHousehold(int parentUserId, int childUserId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE parentUserId = @parentUserId AND childUserId = @childUserId";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("parentUserId", parentUserId);
ctext.Parameters.AddWithValue("childUserId", childUserId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
/// <summary>
/// Delete all household records when a parent user is deleted.
/// </summary>
/// <param name="parentUserId"></param>
/// <returns></returns>
public bool DeleteAllHouseholdRecordsByParentUserId(int parentUserId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE parentUserId = @parentUserId";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("parentUserId", parentUserId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
/// <summary>
/// Delete all household records when a child user is deleted.
/// </summary>
/// <param name="childUserId"></param>
/// <returns></returns>
public bool DeleteAllHouseholdRecordsByChildUserId(int childUserId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE childUserId = @childUserId";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("childUserId", childUserId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View 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 SCHEMA IF NOT EXISTS app; 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;
}
}
}
}

View 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 SCHEMA IF NOT EXISTS app; 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();
}
}
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ReminderRecordDataAccess : IReminderRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "reminderrecords";
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.Find(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
return reminderRecords.ToList() ?? new List<ReminderRecord>();
};
}
public ReminderRecord GetReminderRecordById(int reminderRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ReminderRecord>(tableName);
return table.FindById(reminderRecordId);
};
}
public bool DeleteReminderRecordById(int reminderRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ReminderRecord>(tableName);
table.Delete(reminderRecordId);
return true;
};
}
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ReminderRecord>(tableName);
table.Upsert(reminderRecord);
return true;
};
}
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.DeleteMany(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ServiceRecordDataAccess: IServiceRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "servicerecords";
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.Find(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
return serviceRecords.ToList() ?? new List<ServiceRecord>();
};
}
public ServiceRecord GetServiceRecordById(int serviceRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ServiceRecord>(tableName);
return table.FindById(serviceRecordId);
};
}
public bool DeleteServiceRecordById(int serviceRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ServiceRecord>(tableName);
table.Delete(serviceRecordId);
return true;
};
}
public bool SaveServiceRecordToVehicle(ServiceRecord serviceRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ServiceRecord>(tableName);
table.Upsert(serviceRecord);
return true;
};
}
public bool DeleteAllServiceRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.DeleteMany(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class TaxRecordDataAccess : ITaxRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "taxrecords";
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.Find(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
return taxRecords.ToList() ?? new List<TaxRecord>();
};
}
public TaxRecord GetTaxRecordById(int taxRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<TaxRecord>(tableName);
return table.FindById(taxRecordId);
};
}
public bool DeleteTaxRecordById(int taxRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<TaxRecord>(tableName);
table.Delete(taxRecordId);
return true;
};
}
public bool SaveTaxRecordToVehicle(TaxRecord taxRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<TaxRecord>(tableName);
table.Upsert(taxRecord);
return true;
};
}
public bool DeleteAllTaxRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.DeleteMany(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -1,57 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class UpgradeRecordDataAccess : IUpgradeRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "upgraderecords";
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.Find(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
return upgradeRecords.ToList() ?? new List<UpgradeRecord>();
};
}
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<UpgradeRecord>(tableName);
return table.FindById(upgradeRecordId);
};
}
public bool DeleteUpgradeRecordById(int upgradeRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Delete(upgradeRecordId);
return true;
};
}
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Upsert(upgradeRecord);
return true;
};
}
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.DeleteMany(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -1,46 +0,0 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class VehicleDataAccess: IVehicleDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "vehicles";
public bool SaveVehicle(Vehicle vehicle)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Vehicle>(tableName);
table.Upsert(vehicle);
return true;
};
}
public bool DeleteVehicle(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Vehicle>(tableName);
return table.Delete(vehicleId);
};
}
public List<Vehicle> GetVehicles()
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Vehicle>(tableName);
return table.FindAll().ToList();
};
}
public Vehicle GetVehicleById(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<Vehicle>(tableName);
return table.FindById(vehicleId);
};
}
}
}

View File

@ -0,0 +1,11 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IExtraFieldDataAccess
{
public List<RecordExtraField> GetExtraFields();
public RecordExtraField GetExtraFieldsById(int importMode);
public bool SaveExtraFields(RecordExtraField record);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IInspectionRecordDataAccess
{
public List<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId);
public InspectionRecord GetInspectionRecordById(int inspectionRecordId);
public bool DeleteInspectionRecordById(int inspectionRecordId);
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId);
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IInspectionRecordTemplateDataAccess
{
public List<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId);
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId);
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId);
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate);
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId);
}
}

View File

@ -4,8 +4,10 @@ namespace CarCareTracker.External.Interfaces
{
public interface INoteDataAccess
{
public Note GetNoteByVehicleId(int vehicleId);
public bool SaveNoteToVehicleId(Note note);
bool DeleteNoteByVehicleId(int vehicleId);
public List<Note> GetNotesByVehicleId(int vehicleId);
public Note GetNoteById(int noteId);
public bool SaveNoteToVehicle(Note note);
public bool DeleteNoteById(int noteId);
public bool DeleteAllNotesByVehicleId(int vehicleId);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IOdometerRecordDataAccess
{
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId);
public OdometerRecord GetOdometerRecordById(int odometerRecordId);
public bool DeleteOdometerRecordById(int odometerRecordId);
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord);
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IPlanRecordDataAccess
{
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId);
public PlanRecord GetPlanRecordById(int planRecordId);
public bool DeletePlanRecordById(int planRecordId);
public bool SavePlanRecordToVehicle(PlanRecord planRecord);
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IPlanRecordTemplateDataAccess
{
public List<PlanRecordInput> GetPlanRecordTemplatesByVehicleId(int vehicleId);
public PlanRecordInput GetPlanRecordTemplateById(int planRecordId);
public bool DeletePlanRecordTemplateById(int planRecordId);
public bool SavePlanRecordTemplateToVehicle(PlanRecordInput planRecord);
public bool DeleteAllPlanRecordTemplatesByVehicleId(int vehicleId);
}
}

Some files were not shown because too many files have changed in this diff Show More