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())