diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate index 45ce814..4934839 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 1d67c13..bcd2102 100644 --- a/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -8,15 +8,15 @@ BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> @@ -24,15 +24,15 @@ BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> @@ -46,9 +46,9 @@ filePath = "Sources/App/Models/FuelEntry.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "200" - endingLineNumber = "200" - landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:downloaded:tabletserial:)" + startingLineNumber = "203" + endingLineNumber = "203" + landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:downloaded:tabletserial:uniqueID:)" landmarkType = "7"> @@ -174,8 +174,8 @@ filePath = "Sources/App/Models/FuelEntry.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "285" - endingLineNumber = "285" + startingLineNumber = "291" + endingLineNumber = "291" landmarkName = "init(from:)" landmarkType = "7"> @@ -222,76 +222,12 @@ filePath = "Sources/App/Models/FuelEntry.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "291" - endingLineNumber = "291" + startingLineNumber = "297" + endingLineNumber = "297" landmarkName = "init(from:)" landmarkType = "7"> - - - - - - - - - - - - - - - - @@ -318,54 +254,10 @@ filePath = "Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "670" - endingLineNumber = "670" + startingLineNumber = "674" + endingLineNumber = "674" landmarkName = "create(req:)" landmarkType = "7"> - - - - - - - - diff --git a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift index 36ba6ae..67d0ed5 100644 --- a/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift +++ b/Sources/App/Controllers/V2.1/FuelEntryControllerV2_1.swift @@ -649,21 +649,25 @@ struct FuelEntryV2ControllerV2_1: RouteCollection { var results: [FuelEntry] = [] for entry in fuelEntry { let input = try FuelEntry.init(from: entry) - let data = entry.image ?? nil - let length = data?.count - if(data == nil || length == 0){ - input.image = nil - } - if(entry.id != nil){ - if (try await FuelEntry.find(entry.id!, on: req.db)) != nil { - input._$id.exists = true - } else { - input._$idExists = false + + + + let data = entry.image ?? nil + let length = data?.count + if(data == nil || length == 0){ + input.image = nil } - - } - try await input.save(on: req.db) - results.append(input) + if(entry.id != nil){ + if (try await FuelEntry.find(entry.id!, on: req.db)) != nil { + input._$id.exists = true + } else { + input._$idExists = false + } + + } + try await input.save(on: req.db) + results.append(input) + } // try await fuelEntry.create(on: req.db) diff --git a/Sources/App/Controllers/V2.2/FuelEntryControllerV2_2.swift b/Sources/App/Controllers/V2.2/FuelEntryControllerV2_2.swift index e69de29..9c8569c 100644 --- a/Sources/App/Controllers/V2.2/FuelEntryControllerV2_2.swift +++ b/Sources/App/Controllers/V2.2/FuelEntryControllerV2_2.swift @@ -0,0 +1,819 @@ +// +// FuelEntryControllerV2_2.swift +// Fueling +// +// Created by Michiel Carman on 10/26/26. +// + +// +// FuelEntryControllerV2_1.swift +// Fueling +// +// Created by Michiel Carman on 9/15/26. +// + +import Vapor +import FluentKit +import FluentSQL + +struct FuelEntryV2ControllerV2_2: 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 -> Response { + let fuelEntry = try req.content.decode([FuelEntry.V2_2_Input].self) + var results: [FuelEntry] = [] + for entry in fuelEntry { + let input = FuelEntry.init(from: entry) + let exists = try await FuelEntry.query(on: req.db).filter(\.$uniqueID, .equal, input.uniqueID).first() + if(exists == nil) { + let data = entry.image ?? nil + let length = data?.count + if(data == nil || length == 0){ + input.image = nil + } + if(entry.id != nil){ + if (try await FuelEntry.find(entry.id!, on: req.db)) != nil { + input._$id.exists = true + } else { + input._$idExists = false + } + + } + try await input.save(on: req.db) + results.append(input) + } else { + results.append(exists!) + } + + + + } + +// try await fuelEntry.create(on: req.db) + let response = try await results.map{ + try FuelEntry.V2_1_Output.init(from: $0) + }.encodeResponse(for: req) + return response + } + + + + 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.2/v2.2.swift b/Sources/App/DTO/V2.2/v2.2.swift index e69de29..e86c155 100644 --- a/Sources/App/DTO/V2.2/v2.2.swift +++ b/Sources/App/DTO/V2.2/v2.2.swift @@ -0,0 +1,71 @@ +// +// v2.2.swift +// Fueling +// +// Created by Michiel Carman on 10/26/26. +// + +import Vapor + +extension FuelEntry { + struct V2_2_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? + var userId: String? + var uniqueID: String? + } + + struct V2_2_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? + var uniqueID: String? + 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 + } + self.uniqueID = decoder.uniqueID?.uuidString + } + } + +} + diff --git a/Sources/App/Migrations/CreateFuelEntry.swift b/Sources/App/Migrations/CreateFuelEntry.swift index a579dfa..0c9181e 100755 --- a/Sources/App/Migrations/CreateFuelEntry.swift +++ b/Sources/App/Migrations/CreateFuelEntry.swift @@ -96,6 +96,20 @@ struct AddDownloadedBool: AsyncMigration { } } +struct AddUniqueIDFiled: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema("fuel_entries") + .field("unique_id", .uuid) + .update() + } + + func revert(on database: Database) async throws { + try await database.schema("fuel_entries") + .deleteField("unique_id") + .update() + } +} + struct AddAppVersion: 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 108fba5..7a031ae 100755 --- a/Sources/App/Models/FuelEntry.swift +++ b/Sources/App/Models/FuelEntry.swift @@ -115,46 +115,46 @@ final class FuelEntry: Model, Content { return entry } } - + @ID(key: .id) var id: UUID? - + @Field(key: "timestamp") var timestamp: Date - + @Field(key: "user_id") var userID: String - + @Field(key: "latitude") var latitude: Double @Field(key: "longitude") var longitude: Double - + @Field(key: "image") var image: Data? - + @Field(key: "product") var product: String - + @Field(key: "make") var make: String - + @Field(key: "model") var model: String - + @Field(key: "serial_number") var serialNumber: String - + @Field(key: "tank_id") var tankID: String - + @Field(key: "fuel_amount") var fuelAmount: Double - + @Field(key: "fuel_type") var fuelType: String - + @Field(key: "is_override") var isOverride: Bool @@ -170,11 +170,13 @@ final class FuelEntry: Model, Content { @Timestamp(key: "loadDate", on: .create) var loaddate: Date? @Timestamp(key: "pullDate", on: .none) var pulldate: Date? -// var imageData: Data? - + @Field(key: "unique_id") var uniqueID: UUID? + + // 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? + 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, uniqueID: UUID? = nil) { //imageData: Data? self.id = id self.timestamp = timestamp self.userID = userID @@ -193,10 +195,11 @@ final class FuelEntry: Model, Content { self.image = image ?? nil self.tabletserial = tabletserial ?? nil self.appVersion = appversion ?? nil -// self.imageData = imageData + self.uniqueID = uniqueID + // 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? + + 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, uniqueID: UUID? = nil) { //imageData: Data? self.id = id self.timestamp = timestamp self.userID = userID @@ -214,9 +217,10 @@ final class FuelEntry: Model, Content { self.downloaded = downloaded ?? nil self.image = image ?? nil self.tabletserial = tabletserial ?? nil -// self.imageData = imageData + self.uniqueID = uniqueID + // 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, tabletserial: String? = nil) { //imageData: Data? + 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, tabletserial: String? = nil, uniqueID: UUID? = nil) { //imageData: Data? self.id = id self.timestamp = timestamp self.userID = userID @@ -234,9 +238,10 @@ final class FuelEntry: Model, Content { self.downloaded = downloaded ?? nil self.image = image ?? nil self.tabletserial = tabletserial ?? nil -// self.imageData = imageData + self.uniqueID = uniqueID + // self.imageData = imageData } - init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, tabletserial: String? = nil) { //imageData: Data? + init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, tabletserial: String? = nil, uniqueID: UUID? = nil) { //imageData: Data? self.id = id self.timestamp = timestamp self.userID = userID @@ -252,26 +257,27 @@ final class FuelEntry: Model, Content { self.fuelType = fuelType self.isOverride = isOverride self.tabletserial = tabletserial ?? nil -// self.imageData = imageData + self.uniqueID = uniqueID + // 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? -// } + // 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 @@ -292,4 +298,25 @@ final class FuelEntry: Model, Content { self.tabletserial = decoder.tabletSerial ?? "" self.downloaded = false } + + init(from decoder: FuelEntry.V2_2_Input) { + 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.userID = decoder.userId ?? "" + self.image = decoder.image + self.tabletserial = decoder.tabletSerial ?? "" + self.downloaded = false + self.uniqueID = decoder.uniqueID == nil ? nil : UUID(uuidString: decoder.uniqueID!) + } } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 92901ff..ce8affc 100755 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -52,6 +52,7 @@ public func configure(_ app: Application) async throws { app.migrations.add(AddLoadDateAndPullDate()) app.migrations.add(AddDeviceSerial()) app.migrations.add(AddAppVersion()) + app.migrations.add(AddUniqueIDFiled()) try await app.autoMigrate().get() // register routes