diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate
index fd9a40b..27ae840 100644
Binary files a/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index 5a07562..0776a4d 100644
--- a/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -14,8 +14,8 @@
filePath = "Sources/App/Models/FuelEntry.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "215"
- endingLineNumber = "215"
+ startingLineNumber = "240"
+ endingLineNumber = "240"
landmarkName = "init(id:timestamp:userID:latitude:longitude:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)"
landmarkType = "7">
@@ -30,8 +30,8 @@
filePath = "Sources/App/Models/FuelEntry.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "195"
- endingLineNumber = "195"
+ startingLineNumber = "220"
+ endingLineNumber = "220"
landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)"
landmarkType = "7">
@@ -40,14 +40,14 @@
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
@@ -148,5 +148,21 @@
landmarkType = "7">
+
+
+
+
diff --git a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift
index 6ee914a..044107d 100644
--- a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift
+++ b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift
@@ -1,7 +1,799 @@
//
-// FuelEntryControllerV2.1.swift
+// FuelEntryControllerV2_1.swift
// Fueling
//
// Created by Michiel Carman on 9/15/26.
//
+import Vapor
+import FluentKit
+import FluentSQL
+
+struct FuelEntryV2ControllerV2_1: RouteCollection {
+ func boot(routes: RoutesBuilder) throws {
+ let fuelEntries = routes.grouped("fuel_entries")
+ let tokenAuthMiddleware = Token.authenticator()
+ let guardMiddleware = User.guardMiddleware()
+ let tokenAuthGroup = fuelEntries.grouped(tokenAuthMiddleware, guardMiddleware)
+ tokenAuthGroup.get(use: filterLoadDate)
+ tokenAuthGroup.get("main", use: filterLoadDate)
+ tokenAuthGroup.get("timestamp", use: filterTimeStamp)
+ tokenAuthGroup.get("images", use: filterImages)
+ tokenAuthGroup.get("test", use: filtered)
+ tokenAuthGroup.get("rpm", use: rpm)
+ tokenAuthGroup.get("maxim", use: filtered)
+ tokenAuthGroup.post(use: create)
+ tokenAuthGroup.put( use: softdelete)
+ tokenAuthGroup.group(":fuelEntryID") { fuelEntry in
+ fuelEntry.delete(use: delete)
+ }
+
+ }
+ @Sendable
+ func index(req: Request) async throws -> Response {
+ return try await FuelEntry.query(on: req.db).all().map{
+ try FuelEntry.V2_1_Output.init(from: $0)
+ }.encodeResponse(for: req)
+ }
+
+ struct filteredreturn: Content{
+ var fuelentries: [FuelEntry]
+ var pages: Int
+ }
+
+ struct dload: Content{
+ var id: UUID
+ }
+
+ @Sendable
+ func softdelete(req: Request) async throws -> Response {
+ let ids = try req.content.decode([dload].self)
+ var msg: [String] = []
+ for id in ids {
+ guard let fe = try await FuelEntry.find(id.id, on: req.db) else {
+ msg.append("create error message for item")
+ continue
+ }
+ fe.downloaded = true
+ try await fe.save(on: req.db)
+ }
+ let response = Response(status: .ok, body: "good")
+ return response
+ }
+
+ fileprivate func queryParamExists(_ req: Request, _ param: String) async throws -> Bool {
+ let x = try req.query.get(Bool.self, at: param) ? true : false
+
+ print ("x: \(x)")
+ return x
+
+ }
+
+ fileprivate func parseDate2(_ dateString: String, _ start: Bool = true) throws -> Date? {
+ let currentDate = Calendar.current.startOfDay(for: Date())
+ if(dateString.count == 0 && start){
+ if(start) {
+ return Calendar.current.date(byAdding: .day, value: -2, to: currentDate)
+ } else {
+ return currentDate
+ }
+ } else {
+ let parts = dateString.split(separator: "-")
+ if(parts.count != 3) {
+ return nil
+ }
+ let year = Int(parts[0]) ?? nil
+ if(year == nil) {
+ return nil
+ }
+ let month = Int(parts[1]) ?? nil
+ if(month == nil) {
+ return nil
+ }
+ let day = Int(parts[2]) ?? nil
+ if( day == nil ) {
+ return nil
+ }
+ var dateComponents = DateComponents()
+ dateComponents.year = year
+ dateComponents.month = month
+ dateComponents.day = day
+ if(start) {
+ dateComponents.hour = 0
+ dateComponents.minute = 0
+ dateComponents.second = 0
+ } else {
+ dateComponents.hour = 23
+ dateComponents.minute = 59
+ dateComponents.second = 59
+ }
+ let calendar = Calendar(identifier: .gregorian)
+ return calendar.date(from: dateComponents)
+ }
+ }
+
+ fileprivate func parseDate(_ dateString: String, _ start: Bool = true) throws -> Date? {
+
+ if(dateString.count == 0){
+ return nil
+ } else {
+ let parts = dateString.split(separator: "-")
+ if(parts.count != 3) {
+ throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted."))
+ }
+ let year = Int(parts[0]) ?? nil
+ if(year == nil) {
+ throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Year component must be in the format yyyy and must be an integer"))
+ }
+ let month = Int(parts[1]) ?? nil
+ if(month == nil) {
+ throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Month component must be in the format MM and must be an integer"))
+ }
+ let day = Int(parts[2]) ?? nil
+ if( day == nil ) {
+ throw Abort(.custom(code: 400,reasonPhrase: "Value at path '\(start ? "startdate" : "enddate")' was not of format 'yyyy-MM-dd'. Data found at '\(start ? "startdate" : "enddate")' was not properly formatted. Day component must be in the format dd and must be an integer"))
+ }
+ var dateComponents = DateComponents()
+ dateComponents.year = year
+ dateComponents.month = month
+ dateComponents.day = day
+ if(start) {
+ dateComponents.hour = 0
+ dateComponents.minute = 0
+ dateComponents.second = 0
+ } else {
+ dateComponents.hour = 23
+ dateComponents.minute = 59
+ dateComponents.second = 59
+ }
+ let calendar = Calendar(identifier: .gregorian)
+ return calendar.date(from: dateComponents)
+ }
+ }
+
+ fileprivate func generateSQLNoImage(_ startdate: Date? = nil, _ enddate: Date? = nil) -> SQLQueryString {
+ if(startdate == nil) {
+ return SQLQueryString("select count(*) as total from \"fuel_entries\";")
+ }
+ if(enddate != nil) {
+ return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate) and \"loadDate\" <= \(bind: enddate) ;")
+ } else {
+ return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate);")
+ }
+ }
+
+ fileprivate func generateSQLImage(_ startdate: Date? = nil, _ enddate: Date? = nil) -> SQLQueryString {
+ var sql: String = ""
+ if(startdate == nil) {
+ return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"image\" is not null;")
+ }
+ if(enddate != nil) {
+ sql = "select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(String(describing: startdate)) and \"loadDate\" <= \(String(describing: enddate)) and \"image\" is not null;"
+ return SQLQueryString("select count(*) as total from \"fuel_entries\" where \"loadDate\" >= \(bind: startdate) and \"loadDate\" <= \(bind: enddate) and \"image\" is not null;")
+ } else {
+ sql = "select count(*) as total from \"fuel_entries\" where \"image\" is not null and \"loadDate\" >= '\(startdate ?? Date())';"
+ return SQLQueryString(sql)
+ }
+ }
+
+ struct CountRecords: Content {
+ let total: Int
+ }
+
+ fileprivate func getCount(_ req: Request, _ sql: SQLQueryString) async throws -> Int {
+ print(sql)
+ if let db = req.db as? SQLDatabase {
+ let answer = try await db
+ .raw(sql)
+ .first(decoding: CountRecords.self)
+ return answer?.total ?? 0
+ } else {
+ return 0
+ }
+ }
+
+ fileprivate func getPaging(_ page: Int, _ per: Int, _ records: Int) async throws -> [String: Any] {
+ var vals: [String: Any] = [:]
+ vals["page"] = page
+ vals["per"] = per
+ vals["records"] = records
+ let lastpage = Int(ceil(Double(records) / Double(per)))
+ vals["lastpage"] = (page == lastpage) ? true : false
+ vals["offset"] = ( page * per) - per
+ return vals
+ }
+
+ fileprivate func getParams(_ req: Request) async throws -> [qparam] {
+ let urlComponents = URLComponents(string: req.url.description)!
+ var params: [qparam] = []
+ for queryItem in urlComponents.queryItems ?? [] {
+ params.append(qparam(name: queryItem.name, value: queryItem.value ?? ""))
+ }
+ return params
+ }
+
+ fileprivate func getMaximError(_ req: Request, _ message: String) async throws -> returnObjV2_1 {
+ let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "")
+ let method = req.method.rawValue
+ let baseurl = "https://fueling.prelub.com"
+ let data : [FuelEntry.V2_1_Output] = []
+ let meta = Meta(page: 0, per: 0, total: 0, lastpage: true, error: true, message: message, method: method, baseurl: baseurl, route: rt, querystring: "" )
+ return returnObjV2_1(data: data, meta: meta)
+
+ }
+
+ @Sendable
+ func filterLoadDate(req: Request) async throws -> Response {
+ let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "")
+ let method = req.method.rawValue
+ let baseurl = "https://fueling.prelub.com"
+
+ let qstring = req.query
+ let components: Dictionary = try req.query.decode(Dictionary.self)
+
+ var startdate : Date?
+ var qs: [String] = []
+ for component in components {
+ qs.append( "\(component.key)=\(component.value ?? "nil")")
+ }
+ if(try qstring.get(String?.self, at: "startdate") != nil) {
+ startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "")
+ if(startdate == nil) {
+ return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req)
+ }
+ } else {
+ //throw error
+ return try await getMaximError(req, "startdate is required").encodeResponse(for: req)
+ }
+ var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(try qstring.get(String?.self, at: "enddate") != nil) {
+ enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(enddate == nil) {
+ return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req)
+ }
+ }
+ let emptyData: Data? = Data()
+ let page = try qstring.get(Int?.self, at: "page") ?? 1
+ let per = try qstring.get(Int?.self, at: "per") ?? 10
+ let qsStr = qs.joined(separator: "&")
+
+ let records = try await getCount(req, generateSQLNoImage(startdate ?? nil, enddate ?? nil) )
+
+ let paging = try await getPaging(page, per, records)
+ let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qsStr)")
+ let query = try await FuelEntry.query(on: req.db)
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ .field(\.$tabletserial)
+ .group(.and) {
+ grp in
+ grp.filter(\.$loaddate >= startdate)
+ if( enddate != nil) {
+ grp.filter(\.$loaddate <= enddate!)
+ }
+ }
+ .sort(\.$loaddate, .ascending)
+ .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int))
+ .all().map{
+ try FuelEntry.V2_1_Output.init(from: $0)
+ }
+ let response = returnObjV2_1(data: query, meta: meta)
+ return try await response.encodeResponse(for: req)
+ }
+
+ @Sendable
+ func filterImages(req: Request) async throws -> Response {
+ let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "")
+ let method = req.method.rawValue
+ let baseurl = "https://fueling.prelub.com"
+ let qstring = req.query
+ let components: Dictionary = try req.query.decode(Dictionary.self)
+ var startdate : Date?
+ var qs: [String] = []
+ for component in components {
+ qs.append( "\(component.key)=\(component.value ?? "nil")")
+ }
+ if(try qstring.get(String?.self, at: "startdate") != nil) {
+ startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "")
+ if(startdate == nil) {
+ return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req)
+ }
+ } else {
+ //throw error
+ return try await getMaximError(req, "startdate is required").encodeResponse(for: req)
+ }
+ var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(try qstring.get(String?.self, at: "enddate") != nil) {
+ enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(enddate == nil) {
+ return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req)
+ }
+ }
+ let page = try qstring.get(Int?.self, at: "page") ?? 1
+ let per = try qstring.get(Int?.self, at: "per") ?? 5
+ var records : Int = 0
+ if(enddate == nil) {
+ records = try await FuelEntry.query(on: req.db)
+ .field(\.$id)
+ .group(.and) {
+ grp in
+ grp.filter(\.$loaddate >= startdate)
+ .filter(\.$image != nil)
+ }
+ .all()
+ .count
+ } else {
+ records = try await FuelEntry.query(on: req.db)
+ .field(\.$id)
+ .group(.and) {
+ grp in
+ grp.filter(\.$loaddate >= startdate)
+ .filter(\.$image != nil)
+ .filter(\.$loaddate <= enddate)
+ }
+ .all()
+ .count
+ }
+ let qsStr = qs.joined(separator: "&")
+
+ let paging = try await getPaging(page, per, records)
+ let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qsStr)")
+ let query = try await FuelEntry.query(on: req.db)
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ .field(\.$image)
+ .field(\.$tabletserial)
+ .group(.and) {
+ grp in if(enddate != nil) {
+ grp.filter(\.$loaddate >= startdate)
+ .filter(\.$image != nil)
+ .filter(\.$loaddate <= enddate!)
+ } else {
+ grp.filter(\.$loaddate >= startdate)
+ .filter(\.$image != nil)
+ }
+ }
+ .sort(\.$loaddate, .ascending)
+ .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int))
+ .all().map{
+ try FuelEntry.V2_1_Output.init(from: $0)
+ }
+ let response = returnObjV2_1(data: query, meta: meta)
+ return try await response.encodeResponse(for: req)
+ }
+
+ @Sendable
+ func filterTimeStamp(req: Request) async throws -> Response {
+ let rt = req.route!.description.replacingOccurrences(of: "GET ", with: "").replacingOccurrences(of: "PUT ", with: "").replacingOccurrences(of: "POST ", with: "").replacingOccurrences(of: "DELETE ", with: "")
+ let method = req.method.rawValue
+ let baseurl = "https://fueling.prelub.com"
+ let qstring = req.query
+ let components: Dictionary = try req.query.decode(Dictionary.self)
+ var startdate : Date?
+ var qs: [String] = []
+ for component in components {
+ qs.append( "\(component.key)=\(component.value ?? "nil")")
+ }
+ if(try qstring.get(String?.self, at: "startdate") != nil) {
+ startdate = try parseDate2(qstring.get(String?.self, at: "startdate") ?? "")
+ if(startdate == nil) {
+ return try await(getMaximError(req, "startdate is invalid format. YYYY-MM-DD")) .encodeResponse(for: req)
+ }
+ } else {
+ //throw error
+ return try await getMaximError(req, "startdate is required").encodeResponse(for: req)
+ }
+ var enddate : Date?//= try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(try qstring.get(String?.self, at: "enddate") != nil) {
+ enddate = try parseDate2(qstring.get(String?.self, at: "enddate") ?? "", false)
+ if(enddate == nil) {
+ return try await getMaximError(req, "enddate is invalid format. YYYY-MM-DD").encodeResponse(for: req)
+ }
+ }
+ let emptyData: Data? = Data()
+ let page = try qstring.get(Int?.self, at: "page") ?? 1
+ let per = try qstring.get(Int?.self, at: "per") ?? 10
+ let records = try await getCount(req, generateSQLNoImage(startdate ?? nil, enddate ?? nil) )
+ let paging = try await getPaging(page, per, records)
+ let meta = Meta(page: page, per: per, total: records, lastpage: (paging["lastpage"] != nil) ? paging["lastpage"] as! Bool : false, error: false, message: "", method: method, baseurl: baseurl, route: rt, querystring: "?\(qs.joined(separator: "&"))")
+ let query = try await FuelEntry.query(on: req.db)
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ .field(\.$tabletserial)
+ .group(.and) {
+ grp in
+ grp.filter(\.$timestamp >= startdate ?? Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: -2, to: Date())!))
+ if( enddate != nil) {
+ grp.filter(\.$timestamp <= enddate!)
+ }
+ }
+ .sort(\.$loaddate, .ascending)
+ .range(lower: (paging["offset"] as! Int), upper: (paging["offset"] as! Int) + (paging["per"] as! Int))
+ .all().map{
+ try FuelEntry.V2_1_Output.init(from: $0)
+ }
+ let response = returnObjV2_1(data: query, meta: meta)
+ return try await response.encodeResponse(for: req)
+ }
+
+ @Sendable
+ func filtered(req: Request) async throws -> Response {
+ let qstring = req.query
+ let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "")
+ let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ let image = try qstring.get(Bool?.self, at: "image") ?? false
+ let serial = try qstring.get(String?.self, at: "serial") ?? nil
+ let tank = try qstring.get(String?.self, at: "tank") ?? nil
+ let previous = try qstring.get(Bool?.self, at: "previous") ?? false
+
+
+
+ var query = FuelEntry.query(on: req.db)
+ if(image){
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$image)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ .field(\.$tabletserial)
+ //.filter(\.$image != nil)
+
+ } else {
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$tabletserial)
+ .field(\.$loaddate)
+ }
+
+ query = query.group(.and){
+ grp in
+ if( startdate != nil) {
+ grp.filter(\.$loaddate >= startdate)
+ }
+ if( enddate != nil) {
+ grp.filter(\.$loaddate <= enddate!)
+ }
+ if( serial != nil) {
+ grp.filter(\.$serialNumber == serial!)
+ }
+ if( tank != nil) {
+ grp.filter(\.$tankID == tank!)
+ }
+ if(!previous){
+ grp.filter(\.$pulldate == nil)
+ }
+
+ }
+ query = query.sort(\.$loaddate, .ascending)
+
+ if(image){
+ //query = query.filter(\.$image)
+ let wimage = try await query.paginate(for: req).map{
+ img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: previous == true ? img.pulldate : Date())
+ }
+ if(!previous){
+ wimage.items.forEach{
+ _ = $0.toFuelEntry().save(on: req.db)
+ }
+ }
+
+ return try await wimage.encodeResponse(for: req)
+ } else {
+ let woimage = try await query.paginate(for: req).map{
+ img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: previous == true ? img.pulldate : Date(), useremail: img.tabletserial)
+ }
+ if(!previous) {
+ woimage.items.forEach {
+ _ = $0.toFuelEntry().save(on: req.db)
+ }
+ }
+
+ return try await woimage.encodeResponse(for: req)
+ }
+ }
+
+ @Sendable
+ func rpm(req: Request) async throws -> Response {
+ let qstring = req.query
+ let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "")
+ let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ let image = try qstring.get(Bool?.self, at: "image") ?? false
+ let serial = try qstring.get(String?.self, at: "serial") ?? nil
+ let tank = try qstring.get(String?.self, at: "tank") ?? nil
+ let previous = try qstring.get(Bool?.self, at: "previous") ?? false
+
+
+ var query = FuelEntry.query(on: req.db)
+ if(image){
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$image)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+
+
+ } else {
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ }
+
+ query = query.group(.and){
+ grp in
+ if( startdate != nil) {
+ grp.filter(\.$loaddate >= startdate!)
+ }
+ if( enddate != nil) {
+ grp.filter(\.$loaddate <= enddate!)
+ }
+ if( serial != nil) {
+ grp.filter(\.$serialNumber == serial!)
+ }
+ if( tank != nil) {
+ grp.filter(\.$tankID == tank!)
+ }
+ if(!previous){
+ grp.filter(\.$pulldate == nil)
+ }
+
+ }
+ query = query.sort(\.$loaddate, .ascending).sort(\.$timestamp).sort(\.$serialNumber)
+
+ if(image){
+ //query = query.filter(\.$image)
+ let wimage = try await query.paginate(for: req).map{
+ img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date())
+ }
+ return try await wimage.encodeResponse(for: req)
+ } else {
+ let woimage = try await query.paginate(for: req).map{
+ img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date(), useremail: img.tabletserial)
+ }
+ return try await woimage.encodeResponse(for: req)
+ }
+ }
+
+
+
+ func create(req: Request) async throws -> [FuelEntry] {
+ let fuelEntry = try req.content.decode([FuelEntry].self)
+ for entry in fuelEntry {
+ let data = entry.image ?? nil
+ let length = data?.count
+ if(data == nil || length == 0){
+ entry.image = nil
+ }
+ if(entry.id != nil){
+ let dbentry = try await FuelEntry.find(entry.id!, on: req.db)
+ if(dbentry != nil){
+ entry._$idExists = true
+ }
+
+ }
+ entry.save(on: req.db).whenFailure({ error in
+
+ })
+ }
+
+// try await fuelEntry.create(on: req.db)
+ return fuelEntry
+ }
+
+
+
+ func delete(req: Request) async throws -> HTTPStatus {
+ guard let fuelEntry = try await FuelEntry.find(req.parameters.get("fuelEntryID"), on: req.db) else {
+ throw Abort(.notFound)
+ }
+ try await fuelEntry.delete(on: req.db)
+ return .noContent
+ }
+
+ @Sendable
+ func getrpm(req: Request) async throws -> Response {
+ let qstring = req.query
+ let startdate = try parseDate(qstring.get(String?.self, at: "startdate") ?? "")
+ let enddate = try parseDate(qstring.get(String?.self, at: "enddate") ?? "", false)
+ let image = try qstring.get(Bool?.self, at: "image") ?? false
+ let serial = try qstring.get(String?.self, at: "serial") ?? nil
+ let tank = try qstring.get(String?.self, at: "tank") ?? nil
+ let previous = try qstring.get(Bool?.self, at: "previous") ?? false
+
+
+ var query = FuelEntry.query(on: req.db)
+ if(image){
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$image)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+
+
+ } else {
+ query = query
+ .field(\.$id)
+ .field(\.$timestamp)
+ .field(\.$userID)
+ .field(\.$product)
+ .field(\.$make)
+ .field(\.$model)
+ .field(\.$serialNumber)
+ .field(\.$tankID)
+ .field(\.$fuelAmount)
+ .field(\.$fuelType)
+ .field(\.$isOverride)
+ .field(\.$latitude)
+ .field(\.$longitude)
+ .field(\.$downloaded)
+ .field(\.$loaddate)
+ }
+
+ query = query.group(.and){
+ grp in
+ if( startdate != nil) {
+ grp.filter(\.$loaddate >= startdate!)
+ }
+ if( enddate != nil) {
+ grp.filter(\.$loaddate <= enddate!)
+ }
+ if( serial != nil) {
+ grp.filter(\.$serialNumber == serial!)
+ }
+ if( tank != nil) {
+ grp.filter(\.$tankID == tank!)
+ }
+ if(!previous){
+ grp.filter(\.$pulldate == nil)
+ }
+
+ }
+ query = query.sort(\.$loaddate, .ascending).sort(\.$timestamp).sort(\.$serialNumber)
+
+
+
+ if(image){
+ //query = query.filter(\.$image)
+ let wimage = try await query.paginate(for: req).map{
+ img in FuelEntry.wimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, isOverride: img.isOverride, image: img.image == nil ? Data(): img.image, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date())
+
+ }
+ return try await wimage.encodeResponse(for: req)
+ } else {
+ let woimage = try await query.paginate(for: req).map{
+ img in FuelEntry.woimage(id: img.id, timestamp: img.timestamp, userID: img.userID, latitude: img.latitude, longitude: img.longitude, product: img.product, make: img.make, model: img.model, serialNumber: img.serialNumber, tankID: img.tankID, fuelAmount: img.fuelAmount, fuelType: img.fuelType, image: nil, isOverride: img.isOverride, downloaded: img.downloaded == nil ? false : img.downloaded!, loaddate: img.loaddate == nil ? nil : img.loaddate, pulldate: Date(), useremail: img.tabletserial)
+ }
+ return try await woimage.encodeResponse(for: req)
+ }
+ }
+}
+
+//struct custMeta: Content {
+// let per: Int
+// let page: Int
+// let total: Int
+// let status: String?
+//}
+//
+//struct PageRequest: Content {
+// var page: Int
+// var size: Int
+// var status: String?
+// // Add any other properties needed for pagination
+//
+// init(page: Int, size: Int, status: String? = nil) {
+// self.page = page
+// self.size = size
+// self.status = status
+// }
+//}
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/App/DTO/V2.1/FuelEntryDTO.swift b/Sources/App/DTO/V2.1/FuelEntryDTO.swift
index e69de29..1247463 100644
--- a/Sources/App/DTO/V2.1/FuelEntryDTO.swift
+++ b/Sources/App/DTO/V2.1/FuelEntryDTO.swift
@@ -0,0 +1,67 @@
+//
+// FuelEntryDTO.swift
+// Fueling
+//
+// Created by Michiel Carman on 9/15/26.
+//
+
+import Vapor
+
+extension FuelEntry {
+ struct V2_1_Input: Content {
+ var id: UUID?
+ var tabletSerial: String?
+ var fuelAmount: Double?
+ var fuelType: String?
+ var timeStamp: Date?
+ var appVersion: String?
+ var longitude: Double?
+ var product: String?
+ var latitude: Double?
+ var serialNumber: String?
+ var isOverride: Bool?
+ var make: String?
+ var model: String?
+ var tankId: String?
+ var image: Data?
+ }
+
+ struct V2_1_Output: Content {
+ var id: UUID?
+ var tabletSerial: String?
+ var fuelAmount: Double?
+ var fuelType: String?
+ var timeStamp: Date?
+ var appVersion: String?
+ var longitude: Double?
+ var product: String?
+ var latitude: Double?
+ var serialNumber: String?
+ var isOverride: Bool?
+ var make: String?
+ var model: String?
+ var tankId: String?
+ var image: Data?
+ init(from decoder: FuelEntry, img: Bool = false) throws {
+ self.id = decoder.id
+ self.tabletSerial = decoder.tabletserial
+ self.fuelAmount = decoder.fuelAmount
+ self.fuelType = decoder.fuelType
+ self.timeStamp = decoder.timestamp
+ self.appVersion = decoder.appVersion
+ self.longitude = decoder.longitude
+ self.product = decoder.product
+ self.latitude = decoder.latitude
+ self.serialNumber = decoder.serialNumber
+ self.isOverride = decoder.isOverride
+ self.make = decoder.make
+ self.model = decoder.model
+ self.tankId = decoder.tankID
+ if img {
+ self.image = decoder.image
+ }
+ }
+ }
+
+
+}
diff --git a/Sources/App/Helpers/PageHelper.swift b/Sources/App/Helpers/PageHelper.swift
index 2c18690..4982050 100644
--- a/Sources/App/Helpers/PageHelper.swift
+++ b/Sources/App/Helpers/PageHelper.swift
@@ -24,6 +24,11 @@ final class PageHelper {
}
+public struct returnObjV2_1: Content{
+ let data: [FuelEntry.V2_1_Output]
+ let meta: Meta
+}
+
public struct returnObj: Content{
let data: [FuelEntry.Output]
let meta: Meta
diff --git a/Sources/App/Migrations/CreateFuelEntry.swift b/Sources/App/Migrations/CreateFuelEntry.swift
index 7c178f5..a579dfa 100755
--- a/Sources/App/Migrations/CreateFuelEntry.swift
+++ b/Sources/App/Migrations/CreateFuelEntry.swift
@@ -96,6 +96,20 @@ struct AddDownloadedBool: AsyncMigration {
}
}
+struct AddAppVersion: AsyncMigration {
+ func prepare(on database: Database) async throws {
+ try await database.schema("fuel_entries")
+ .field("app_version", .string)
+ .update()
+ }
+
+ func revert(on database: Database) async throws {
+ try await database.schema("fuel_entries")
+ .deleteField("app_version")
+ .update()
+ }
+}
+
struct AddLoadDateAndPullDate: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("fuel_entries")
diff --git a/Sources/App/Models/FuelEntry.swift b/Sources/App/Models/FuelEntry.swift
index fdf7941..eda0fd5 100755
--- a/Sources/App/Models/FuelEntry.swift
+++ b/Sources/App/Models/FuelEntry.swift
@@ -164,12 +164,37 @@ final class FuelEntry: Model, Content {
@Field(key: "tablet_serial")
var tabletserial: String?
+ @Field(key: "app_version")
+ var appVersion: String?
+
@Timestamp(key: "loadDate", on: .create) var loaddate: Date?
@Timestamp(key: "pullDate", on: .none) var pulldate: Date?
// var imageData: Data?
init() {}
+
+ init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, image: Data?, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, downloaded: Bool? = nil, tabletserial: String? = nil, appversion: String? = nil) { //imageData: Data?
+ self.id = id
+ self.timestamp = timestamp
+ self.userID = userID
+ self.latitude = latitude
+ self.longitude = longitude
+ self.image = image ?? nil
+ self.product = product
+ self.make = make
+ self.model = model
+ self.serialNumber = serialNumber
+ self.tankID = tankID
+ self.fuelAmount = fuelAmount
+ self.fuelType = fuelType
+ self.isOverride = isOverride
+ self.downloaded = downloaded ?? nil
+ self.image = image ?? nil
+ self.tabletserial = tabletserial ?? nil
+ self.appVersion = appversion ?? nil
+// self.imageData = imageData
+ }
init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, image: Data?, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, downloaded: Bool? = nil, tabletserial: String? = nil) { //imageData: Data?
self.id = id
@@ -229,4 +254,40 @@ final class FuelEntry: Model, Content {
self.tabletserial = tabletserial ?? nil
// self.imageData = imageData
}
+
+// struct V2_1_Input: Content {
+// var id: UUID?
+// var tabletSerial: Date?
+// var fuelAmount: Double?
+// var fuelType: String?
+// var timeStamp: Date?
+// var appVersion: String?
+// var longitude: String?
+// var product: String?
+// var latitude: String?
+// var serialNumber: String?
+// var isOverride: Bool?
+// var make: String?
+// var model: String?
+// var tankId: String?
+// var image: Data?
+// }
+
+ init(from decoder: FuelEntry.V2_1_Input) throws {
+ self.id = decoder.id
+ self.timestamp = decoder.timeStamp ?? Date()
+ self.fuelAmount = decoder.fuelAmount ?? 0
+ self.fuelType = decoder.fuelType ?? ""
+ self.appVersion = decoder.appVersion ?? ""
+ self.longitude = decoder.longitude ?? 0
+ self.product = decoder.product ?? ""
+ self.latitude = decoder.latitude ?? 0
+ self.serialNumber = decoder.serialNumber ?? ""
+ self.isOverride = decoder.isOverride ?? false
+ self.make = decoder.make ?? ""
+ self.model = decoder.model ?? ""
+ self.tankID = decoder.tankId ?? ""
+ self.image = decoder.image
+
+ }
}
diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift
index a07321d..92901ff 100755
--- a/Sources/App/configure.swift
+++ b/Sources/App/configure.swift
@@ -51,6 +51,7 @@ public func configure(_ app: Application) async throws {
app.migrations.add(UpdateUser3_RemoveTokenfield())
app.migrations.add(AddLoadDateAndPullDate())
app.migrations.add(AddDeviceSerial())
+ app.migrations.add(AddAppVersion())
try await app.autoMigrate().get()
// register routes
diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift
index 6579183..7d42f12 100755
--- a/Sources/App/routes.swift
+++ b/Sources/App/routes.swift
@@ -4,6 +4,8 @@ func routes(_ app: Application) throws {
app.routes.caseInsensitive = true
app.routes.defaultMaxBodySize = "64mb";
+ let V2_1 = app.grouped("api").grouped("v2.1")
+ try V2_1.register(collection: FuelEntryV2ControllerV2_1())
let v2 = app.grouped("api").grouped("v2")
try v2.register(collection: FuelEntryV2Controller())
try v2.register(collection: UserController())