| @@ -0,0 +1,152 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <Bucket | |||||
| uuid = "83CCDCAB-5627-45C3-BC68-1A59A1D654E0" | |||||
| type = "1" | |||||
| version = "2.0"> | |||||
| <Breakpoints> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "1165C094-7F83-4580-9E1E-36ACC4A42F04" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Models/FuelEntry.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "215" | |||||
| endingLineNumber = "215" | |||||
| landmarkName = "init(id:timestamp:userID:latitude:longitude:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "F812306B-8399-447C-84BE-4AC7A776D420" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Models/FuelEntry.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "195" | |||||
| endingLineNumber = "195" | |||||
| landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:tabletserial:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "71BCB6D1-5BA1-44A4-B4BB-3CF6616E1B45" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Models/FuelEntry.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "175" | |||||
| endingLineNumber = "175" | |||||
| landmarkName = "init(id:timestamp:userID:latitude:longitude:image:product:make:model:serialNumber:tankID:fuelAmount:fuelType:isOverride:downloaded:tabletserial:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "D9ECF7FA-C31F-4E3E-A5E0-09B15672960B" | |||||
| shouldBeEnabled = "No" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "469" | |||||
| endingLineNumber = "469" | |||||
| landmarkName = "filtered(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "FCC6E6E2-0876-4286-93D8-266C58EAE0FE" | |||||
| shouldBeEnabled = "No" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "491" | |||||
| endingLineNumber = "491" | |||||
| landmarkName = "filtered(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "0BD134C2-3EF8-4F87-A31F-13B3E66DE82C" | |||||
| shouldBeEnabled = "No" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "511" | |||||
| endingLineNumber = "511" | |||||
| landmarkName = "filtered(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "44A1C379-F3B1-438B-9427-C87E053877C5" | |||||
| shouldBeEnabled = "No" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "529" | |||||
| endingLineNumber = "529" | |||||
| landmarkName = "filtered(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "9959EDC1-617C-4BD3-A47D-FA6774BA18A9" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "547" | |||||
| endingLineNumber = "547" | |||||
| landmarkName = "filtered(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "C4228ECF-D80C-49F9-BBD8-A33956D74C07" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/FuelEntryV2Controller.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "291" | |||||
| endingLineNumber = "291" | |||||
| landmarkName = "filterLoadDate(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| </Breakpoints> | |||||
| </Bucket> | |||||
| @@ -0,0 +1,14 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||||
| <plist version="1.0"> | |||||
| <dict> | |||||
| <key>SchemeUserState</key> | |||||
| <dict> | |||||
| <key>Fueling.xcscheme_^#shared#^_</key> | |||||
| <dict> | |||||
| <key>orderHint</key> | |||||
| <integer>0</integer> | |||||
| </dict> | |||||
| </dict> | |||||
| </dict> | |||||
| </plist> | |||||
| @@ -0,0 +1,24 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <Bucket | |||||
| uuid = "D3C706B5-9CBD-4F91-8A68-CF08562866B5" | |||||
| type = "1" | |||||
| version = "2.0"> | |||||
| <Breakpoints> | |||||
| <BreakpointProxy | |||||
| BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> | |||||
| <BreakpointContent | |||||
| uuid = "65DB5B80-779D-462A-A47F-A0A75B1C75F8" | |||||
| shouldBeEnabled = "Yes" | |||||
| ignoreCount = "0" | |||||
| continueAfterRunningActions = "No" | |||||
| filePath = "Sources/App/Controllers/UserController.swift" | |||||
| startingColumnNumber = "9223372036854775807" | |||||
| endingColumnNumber = "9223372036854775807" | |||||
| startingLineNumber = "35" | |||||
| endingLineNumber = "35" | |||||
| landmarkName = "create(req:)" | |||||
| landmarkType = "7"> | |||||
| </BreakpointContent> | |||||
| </BreakpointProxy> | |||||
| </Breakpoints> | |||||
| </Bucket> | |||||
| @@ -1,6 +1,6 @@ | |||||
| // | // | ||||
| // FuelEntryController.swift | // FuelEntryController.swift | ||||
| // | |||||
| // | |||||
| // | // | ||||
| // Created by Bill Ryckman on 4/24/23. | // Created by Bill Ryckman on 4/24/23. | ||||
| // | // | ||||
| @@ -20,13 +20,13 @@ struct FuelEntryController: RouteCollection { | |||||
| func index(req: Request) async throws -> [FuelEntry] { | func index(req: Request) async throws -> [FuelEntry] { | ||||
| try await FuelEntry.query(on: req.db).all() | try await FuelEntry.query(on: req.db).all() | ||||
| } | } | ||||
| func create(req: Request) async throws -> [FuelEntry] { | func create(req: Request) async throws -> [FuelEntry] { | ||||
| let fuelEntry = try req.content.decode([FuelEntry].self) | let fuelEntry = try req.content.decode([FuelEntry].self) | ||||
| try await fuelEntry.create(on: req.db) | try await fuelEntry.create(on: req.db) | ||||
| return fuelEntry | return fuelEntry | ||||
| } | } | ||||
| func delete(req: Request) async throws -> HTTPStatus { | func delete(req: Request) async throws -> HTTPStatus { | ||||
| guard let fuelEntry = try await FuelEntry.find(req.parameters.get("fuelEntryID"), on: req.db) else { | guard let fuelEntry = try await FuelEntry.find(req.parameters.get("fuelEntryID"), on: req.db) else { | ||||
| throw Abort(.notFound) | throw Abort(.notFound) | ||||
| @@ -39,3 +39,4 @@ struct FuelEntryController: RouteCollection { | |||||
| @@ -0,0 +1,797 @@ | |||||
| // | |||||
| // FuelEntryController.swift | |||||
| // | |||||
| // | |||||
| // Created by Bill Ryckman on 4/24/23. | |||||
| // | |||||
| import Vapor | |||||
| import FluentKit | |||||
| import FluentSQL | |||||
| struct FuelEntryV2Controller: 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) | |||||
| } | |||||
| } | |||||
| func index(req: Request) async throws -> [FuelEntry] { | |||||
| return try await FuelEntry.query(on: req.db).all() | |||||
| } | |||||
| 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 -> returnObj { | |||||
| 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.Output] = [] | |||||
| let meta = Meta(page: 0, per: 0, total: 0, lastpage: true, error: true, message: message, method: method, baseurl: baseurl, route: rt, querystring: "" ) | |||||
| return returnObj(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<String, String?> = try req.query.decode(Dictionary<String, String?>.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{ | |||||
| FuelEntry.Output(id: $0.id, timestamp: $0.timestamp, ssid: $0.userID, latitude: $0.latitude, longitude: $0.longitude, product: $0.product, make: $0.make, model: $0.model, serialNumber: $0.serialNumber, tankID: $0.tankID, fuelAmount: $0.fuelAmount, fuelType: $0.fuelType, isOverride: $0.isOverride, image: emptyData, loaddate: $0.loaddate, useremail: $0.tabletserial ?? "") | |||||
| } | |||||
| let response = returnObj(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<String, String?> = try req.query.decode(Dictionary<String, String?>.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{ | |||||
| FuelEntry.Output(id: $0.id, timestamp: $0.timestamp, ssid: $0.userID, latitude: $0.latitude, longitude: $0.longitude, product: $0.product, make: $0.make, model: $0.model, serialNumber: $0.serialNumber, tankID: $0.tankID, fuelAmount: $0.fuelAmount, fuelType: $0.fuelType, isOverride: $0.isOverride, image: $0.image, loaddate: $0.loaddate, useremail: $0.tabletserial ?? "") | |||||
| } | |||||
| let response = returnObj(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<String, String?> = try req.query.decode(Dictionary<String, String?>.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{ | |||||
| FuelEntry.Output(id: $0.id, timestamp: $0.timestamp, ssid: $0.userID, latitude: $0.latitude, longitude: $0.longitude, product: $0.product, make: $0.make, model: $0.model, serialNumber: $0.serialNumber, tankID: $0.tankID, fuelAmount: $0.fuelAmount, fuelType: $0.fuelType, isOverride: $0.isOverride, image: emptyData, loaddate: $0.loaddate, useremail: $0.tabletserial ?? "") | |||||
| } | |||||
| let response = returnObj(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 | |||||
| } | |||||
| } | |||||
| @@ -1,31 +0,0 @@ | |||||
| import Fluent | |||||
| import Vapor | |||||
| struct TodoController: RouteCollection { | |||||
| func boot(routes: RoutesBuilder) throws { | |||||
| let todos = routes.grouped("todos") | |||||
| todos.get(use: index) | |||||
| todos.post(use: create) | |||||
| todos.group(":todoID") { todo in | |||||
| todo.delete(use: delete) | |||||
| } | |||||
| } | |||||
| func index(req: Request) async throws -> [Todo] { | |||||
| try await Todo.query(on: req.db).all() | |||||
| } | |||||
| func create(req: Request) async throws -> Todo { | |||||
| let todo = try req.content.decode(Todo.self) | |||||
| try await todo.save(on: req.db) | |||||
| return todo | |||||
| } | |||||
| func delete(req: Request) async throws -> HTTPStatus { | |||||
| guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else { | |||||
| throw Abort(.notFound) | |||||
| } | |||||
| try await todo.delete(on: req.db) | |||||
| return .noContent | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/26/24. | |||||
| // | |||||
| import Vapor | |||||
| import Fluent | |||||
| struct UserSignup: Content { | |||||
| let username: String | |||||
| let password: String | |||||
| } | |||||
| struct NewSession: Content { | |||||
| let token: String | |||||
| let user: User.Public | |||||
| } | |||||
| extension UserSignup: Validatable { | |||||
| static func validations(_ validations: inout Validations) { | |||||
| validations.add("username", as: String.self, is: !.empty) | |||||
| validations.add("password", as: String.self, is: .count(6...)) | |||||
| } | |||||
| } | |||||
| struct UserController: RouteCollection { | |||||
| func boot(routes: any RoutesBuilder) throws { | |||||
| let users = routes.grouped("users") | |||||
| users.post(use: create) | |||||
| } | |||||
| @Sendable | |||||
| func create(req: Request) async throws -> Response{ | |||||
| let user = try req.content.decode(User.self) | |||||
| user.passwordHash = try Bcrypt.hash(user.passwordHash) | |||||
| try await user.save(on: req.db) | |||||
| let token = try Token.generate(for: user) | |||||
| try await token.save(on: req.db) | |||||
| return try await token.encodeResponse(for: req) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/26/24. | |||||
| // | |||||
| import Foundation | |||||
| import Vapor | |||||
| enum UserError { | |||||
| case usernameTaken | |||||
| } | |||||
| extension UserError: AbortError { | |||||
| var description: String { | |||||
| reason | |||||
| } | |||||
| var status: HTTPResponseStatus { | |||||
| switch self { | |||||
| case .usernameTaken: return .conflict | |||||
| } | |||||
| } | |||||
| var reason: String { | |||||
| switch self { | |||||
| case .usernameTaken: return "Username already taken" | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| // | |||||
| // PageHelper.swift | |||||
| // Fueling | |||||
| // | |||||
| // Created by Michiel Carman on 5/5/25. | |||||
| // | |||||
| import Foundation | |||||
| import Fluent | |||||
| import Vapor | |||||
| final class PageHelper { | |||||
| // static func paginate<T: Paginate>( | |||||
| // _ query: Query<T>, | |||||
| // page: Int? = nil, | |||||
| // perPage: Int? = nil | |||||
| // ) -> EventLoopFuture<PagedCollection<T>> { | |||||
| // let page = page ?? 1 | |||||
| // let perPage = perPage ?? 10 | |||||
| // return query | |||||
| // .skip((page - 1) * perPage) | |||||
| // } | |||||
| } | |||||
| public struct returnObj: Content{ | |||||
| let data: [FuelEntry.Output] | |||||
| let meta: Meta | |||||
| } | |||||
| public struct Meta: Content{ | |||||
| let page: Int | |||||
| let per: Int | |||||
| let total: Int | |||||
| let lastpage: Bool | |||||
| let error: Bool | |||||
| let message: String? | |||||
| let method: String? | |||||
| let baseurl: String? | |||||
| let route: String? | |||||
| let querystring: String? | |||||
| } | |||||
| public struct qparam: Content{ | |||||
| let name: String | |||||
| let value: String | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| // | // | ||||
| // File.swift | // File.swift | ||||
| // | |||||
| // | |||||
| // | // | ||||
| // Created by Bill Ryckman on 4/26/23. | // Created by Bill Ryckman on 4/26/23. | ||||
| // | // | ||||
| @@ -26,29 +26,103 @@ struct CreateFuelEntry: AsyncMigration { | |||||
| .field("is_override", .bool) | .field("is_override", .bool) | ||||
| .create() | .create() | ||||
| } | } | ||||
| func revert(on database: Database) async throws { | func revert(on database: Database) async throws { | ||||
| try await database.schema("fuel_entries").delete() | try await database.schema("fuel_entries").delete() | ||||
| } | } | ||||
| } | } | ||||
| struct AddLatitudeAndLongitudeAndRemoveGpsLocationAndImage: AsyncMigration { | struct AddLatitudeAndLongitudeAndRemoveGpsLocationAndImage: AsyncMigration { | ||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("latitude", .double) | |||||
| .field("longitude", .double) | |||||
| .deleteField("gps_location") | |||||
| .deleteField("image") | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("latitude") | |||||
| .deleteField("longitude") | |||||
| .field("gps_location", .string) | |||||
| .field("image", .string) | |||||
| .update() | |||||
| } | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("latitude", .double) | |||||
| .field("longitude", .double) | |||||
| .deleteField("gps_location") | |||||
| .deleteField("image") | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("latitude") | |||||
| .deleteField("longitude") | |||||
| .field("gps_location", .string) | |||||
| .field("image", .string) | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct AddImageBackIn: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("image", .string) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("image") | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct StoreImageDataInDatabase: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("image") | |||||
| .field("image", .data) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("image") | |||||
| .field("image", .string) | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct AddDownloadedBool: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("downloaded", .bool) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("downloaded") | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct AddLoadDateAndPullDate: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("loadDate", .datetime) | |||||
| .field("pullDate", .datetime) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("loadDate") | |||||
| .deleteField("pullDate") | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct AddDeviceSerial: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .field("tablet_serial", .string) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("fuel_entries") | |||||
| .deleteField("tablet_serial") | |||||
| .update() | |||||
| } | |||||
| } | } | ||||
| @@ -1,14 +0,0 @@ | |||||
| import Fluent | |||||
| struct CreateTodo: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema("todos") | |||||
| .id() | |||||
| .field("title", .string, .required) | |||||
| .create() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema("todos").delete() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/26/24. | |||||
| // | |||||
| import Foundation | |||||
| import Vapor | |||||
| import Fluent | |||||
| struct CreateTokens: Migration { | |||||
| func prepare(on database: Database) -> EventLoopFuture<Void> { | |||||
| // 2 | |||||
| database.schema(Token.schema) | |||||
| // 3 | |||||
| .id() | |||||
| .field("user_id", .uuid, .references("user", "id")) | |||||
| .field("value", .string, .required) | |||||
| .unique(on: "value") | |||||
| .field("source", .int, .required) | |||||
| .field("created_at", .datetime, .required) | |||||
| .field("expires_at", .datetime) | |||||
| .create() | |||||
| } | |||||
| // 5 | |||||
| func revert(on database: Database) -> EventLoopFuture<Void> { | |||||
| database.schema(Token.schema).delete() | |||||
| } | |||||
| } | |||||
| struct UpdateTokens1: Migration { | |||||
| func prepare(on database: Database) -> EventLoopFuture<Void> { | |||||
| // 2 | |||||
| database.schema(Token.schema) | |||||
| // 3 | |||||
| .deleteField("source") | |||||
| .deleteField("created_at") | |||||
| .deleteField("expires_at") | |||||
| .update() | |||||
| } | |||||
| // 5 | |||||
| func revert(on database: Database) -> EventLoopFuture<Void> { | |||||
| database.schema(Token.schema) | |||||
| .field("source", .int, .required) | |||||
| .field("created_at", .datetime, .required) | |||||
| .field("expires_at", .datetime) | |||||
| .update() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,69 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/26/24. | |||||
| // | |||||
| import Foundation | |||||
| import Vapor | |||||
| import Fluent | |||||
| struct CreateUser: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .id() | |||||
| .field("username", .string, .required) | |||||
| .unique(on: "username") | |||||
| .field("password_hash", .string, .required) | |||||
| .field("created_at", .datetime, .required) | |||||
| .field("updated_at", .datetime, .required) | |||||
| .create() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema(User.schema).delete() | |||||
| } | |||||
| } | |||||
| struct UpdateUser1: AsyncMigration{ | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .deleteField("created_at") | |||||
| .deleteField("updated_at") | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .field("created_at", .datetime, .required) | |||||
| .field("updated_at", .datetime, .required) | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct UpdateUser2_AddTokenfield: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .field("token", .string) | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .deleteField("token") | |||||
| .update() | |||||
| } | |||||
| } | |||||
| struct UpdateUser3_RemoveTokenfield: AsyncMigration { | |||||
| func prepare(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .deleteField("token") | |||||
| .update() | |||||
| } | |||||
| func revert(on database: Database) async throws { | |||||
| try await database.schema(User.schema) | |||||
| .field("token", .string) | |||||
| .update() | |||||
| } | |||||
| } | |||||
| @@ -10,6 +10,111 @@ import Vapor | |||||
| final class FuelEntry: Model, Content { | final class FuelEntry: Model, Content { | ||||
| static let schema = "fuel_entries" | static let schema = "fuel_entries" | ||||
| struct woimage: Content{ | |||||
| let id: UUID? | |||||
| let timestamp: Date | |||||
| let userID: String | |||||
| let latitude: Double | |||||
| let longitude: Double | |||||
| let product: String | |||||
| let make: String | |||||
| let model: String | |||||
| let serialNumber: String | |||||
| let tankID: String | |||||
| let fuelAmount: Double | |||||
| let fuelType: String | |||||
| let image: Data? | |||||
| let isOverride: Bool | |||||
| let downloaded: Bool? | |||||
| let loaddate: Date? | |||||
| let pulldate: Date? | |||||
| let useremail: String? | |||||
| func toFuelEntry() -> FuelEntry { | |||||
| let entry = FuelEntry() | |||||
| entry.id = self.id | |||||
| entry.timestamp = self.timestamp | |||||
| entry.userID = self.userID | |||||
| entry.latitude = self.latitude | |||||
| entry.longitude = self.longitude | |||||
| entry.product = self.product | |||||
| entry.make = self.make | |||||
| entry.model = self.model | |||||
| entry.serialNumber = self.serialNumber | |||||
| entry.tankID = self.tankID | |||||
| entry.fuelAmount = self.fuelAmount | |||||
| entry.fuelType = self.fuelType | |||||
| entry.isOverride = self.isOverride | |||||
| entry.image = nil | |||||
| entry.downloaded = self.downloaded | |||||
| entry.loaddate = self.loaddate | |||||
| entry.pulldate = self.pulldate | |||||
| entry._$idExists = self.id == nil ? false : true | |||||
| entry.tabletserial = self.useremail | |||||
| return entry | |||||
| } | |||||
| } | |||||
| struct Output: Content { | |||||
| let id: UUID? | |||||
| let timestamp: Date | |||||
| let ssid: String | |||||
| let latitude: Double | |||||
| let longitude: Double | |||||
| let product: String | |||||
| let make: String | |||||
| let model: String | |||||
| let serialNumber: String | |||||
| let tankID: String | |||||
| let fuelAmount: Double | |||||
| let fuelType: String | |||||
| let isOverride: Bool | |||||
| let image: Data? | |||||
| let loaddate: Date? | |||||
| let useremail: String? | |||||
| } | |||||
| struct wimage: Content{ | |||||
| let id: UUID? | |||||
| let timestamp: Date | |||||
| let userID: String | |||||
| let latitude: Double | |||||
| let longitude: Double | |||||
| let product: String | |||||
| let make: String | |||||
| let model: String | |||||
| let serialNumber: String | |||||
| let tankID: String | |||||
| let fuelAmount: Double | |||||
| let fuelType: String | |||||
| let isOverride: Bool | |||||
| let image: Data? | |||||
| let downloaded: Bool? | |||||
| let loaddate: Date? | |||||
| let pulldate: Date? | |||||
| func toFuelEntry() -> FuelEntry { | |||||
| let entry = FuelEntry() | |||||
| entry.id = self.id | |||||
| entry.timestamp = self.timestamp | |||||
| entry.userID = self.userID | |||||
| entry.latitude = self.latitude | |||||
| entry.longitude = self.longitude | |||||
| entry.product = self.product | |||||
| entry.make = self.make | |||||
| entry.model = self.model | |||||
| entry.serialNumber = self.serialNumber | |||||
| entry.tankID = self.tankID | |||||
| entry.fuelAmount = self.fuelAmount | |||||
| entry.fuelType = self.fuelType | |||||
| entry.isOverride = self.isOverride | |||||
| entry.image = self.image | |||||
| entry.downloaded = self.downloaded | |||||
| entry.loaddate = self.loaddate | |||||
| entry.pulldate = self.pulldate | |||||
| entry._$idExists = self.id == nil ? false : true | |||||
| return entry | |||||
| } | |||||
| } | |||||
| @ID(key: .id) | @ID(key: .id) | ||||
| var id: UUID? | var id: UUID? | ||||
| @@ -26,8 +131,8 @@ final class FuelEntry: Model, Content { | |||||
| @Field(key: "longitude") | @Field(key: "longitude") | ||||
| var longitude: Double | var longitude: Double | ||||
| // @Field(key: "image") | |||||
| // var image: String | |||||
| @Field(key: "image") | |||||
| var image: Data? | |||||
| @Field(key: "product") | @Field(key: "product") | ||||
| var product: String | var product: String | ||||
| @@ -52,16 +157,67 @@ final class FuelEntry: Model, Content { | |||||
| @Field(key: "is_override") | @Field(key: "is_override") | ||||
| var isOverride: Bool | var isOverride: Bool | ||||
| @Field(key: "downloaded") | |||||
| var downloaded: Bool? | |||||
| @Field(key: "tablet_serial") | |||||
| var tabletserial: String? | |||||
| @Timestamp(key: "loadDate", on: .create) var loaddate: Date? | |||||
| @Timestamp(key: "pullDate", on: .none) var pulldate: Date? | |||||
| // var imageData: Data? | |||||
| init() {} | init() {} | ||||
| 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) { | |||||
| 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 | |||||
| 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.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? | |||||
| 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.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? | |||||
| self.id = id | self.id = id | ||||
| self.timestamp = timestamp | self.timestamp = timestamp | ||||
| self.userID = userID | self.userID = userID | ||||
| self.latitude = latitude | self.latitude = latitude | ||||
| self.longitude = longitude | self.longitude = longitude | ||||
| // self.image = image | |||||
| self.image = image ?? nil | |||||
| self.product = product | self.product = product | ||||
| self.make = make | self.make = make | ||||
| self.model = model | self.model = model | ||||
| @@ -70,5 +226,7 @@ final class FuelEntry: Model, Content { | |||||
| self.fuelAmount = fuelAmount | self.fuelAmount = fuelAmount | ||||
| self.fuelType = fuelType | self.fuelType = fuelType | ||||
| self.isOverride = isOverride | self.isOverride = isOverride | ||||
| self.tabletserial = tabletserial ?? nil | |||||
| // self.imageData = imageData | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,19 +0,0 @@ | |||||
| import Fluent | |||||
| import Vapor | |||||
| final class Todo: Model, Content { | |||||
| static let schema = "todos" | |||||
| @ID(key: .id) | |||||
| var id: UUID? | |||||
| @Field(key: "title") | |||||
| var title: String | |||||
| init() { } | |||||
| init(id: UUID? = nil, title: String) { | |||||
| self.id = id | |||||
| self.title = title | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/24/24. | |||||
| // | |||||
| import Vapor | |||||
| import Fluent | |||||
| final class Token: Model, Content, @unchecked Sendable { | |||||
| static let schema = "token" | |||||
| @ID() | |||||
| var id: UUID? | |||||
| @Parent(key: "user_id") | |||||
| var user: User | |||||
| @Field(key: "value") | |||||
| var value: String | |||||
| init() {} | |||||
| init(id: UUID? = nil, userId: User.IDValue, token: String) { | |||||
| self.id = id | |||||
| self.$user.id = userId | |||||
| self.value = token | |||||
| } | |||||
| } | |||||
| extension Token { | |||||
| static func generate(for user: User) throws -> Token { | |||||
| let random = [UInt8].random(count: 32).base64 | |||||
| return try Token( userId: user.requireID(), token: random) | |||||
| } | |||||
| } | |||||
| extension Token: ModelTokenAuthenticatable { | |||||
| typealias User = App.User | |||||
| static let valueKey = \Token.$value | |||||
| static let userKey = \Token.$user | |||||
| var isValid: Bool { | |||||
| true | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| // | |||||
| // File.swift | |||||
| // | |||||
| // | |||||
| // Created by Michiel Carman on 9/24/24. | |||||
| // | |||||
| import Fluent | |||||
| import Vapor | |||||
| final class User: Model, Content { | |||||
| static let schema = "user" | |||||
| final class Public: Content { | |||||
| var id: UUID? | |||||
| var username: String | |||||
| init(id: UUID?, username: String) { | |||||
| self.id = id | |||||
| self.username = username | |||||
| } | |||||
| } | |||||
| @ID() | |||||
| var id: UUID? | |||||
| @Field(key: "username") | |||||
| var username: String | |||||
| @Field(key: "password_hash") | |||||
| var passwordHash: String | |||||
| // @Timestamp(key: "created_at", on: .create) | |||||
| // var createdAt: Date? | |||||
| // | |||||
| // @Timestamp(key: "updated_at", on: .update) | |||||
| // var updatedAt: Date? | |||||
| init() {} | |||||
| init(id: UUID? = nil, username: String, passwordHash: String) { | |||||
| self.id = id | |||||
| self.username = username | |||||
| self.passwordHash = passwordHash | |||||
| } | |||||
| } | |||||
| extension User { | |||||
| func convertToPublic() -> User.Public { | |||||
| return User.Public(id: id, username: username) | |||||
| } | |||||
| } | |||||
| extension User: ModelAuthenticatable { | |||||
| static let usernameKey = \User.$username | |||||
| static let passwordHashKey = \User.$passwordHash | |||||
| func verify(password: String) throws -> Bool { | |||||
| try Bcrypt.verify(password, created: self.passwordHash) | |||||
| } | |||||
| } | |||||
| @@ -1,18 +1,56 @@ | |||||
| import Fluent | import Fluent | ||||
| import FluentPostgresDriver | import FluentPostgresDriver | ||||
| import NIOSSL | |||||
| import Vapor | import Vapor | ||||
| // configures your application | // configures your application | ||||
| public func configure(_ app: Application) async throws { | public func configure(_ app: Application) async throws { | ||||
| app.middleware.use(ErrorMiddleware.default(environment: app.environment)) | |||||
| // uncomment to serve files from /Public folder | // uncomment to serve files from /Public folder | ||||
| // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) | ||||
| app.routes.defaultMaxBodySize = "1000kb" | |||||
| // let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(300), read: .seconds(300)) | |||||
| // app.http.client.configuration.timeout = timeout | |||||
| app.databases.use(.postgres(hostname: "localhost", username: "mcarman", password: "@ng31F@rm0823262", database: "fueling"), as: .psql) | |||||
| app.http.server.configuration.port = 3001 | |||||
| app.routes.defaultMaxBodySize = "128mb" | |||||
| let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(300), read: .seconds(300)) | |||||
| app.http.client.configuration.timeout = timeout | |||||
| // let envHost = Environment.get("DATABASE_HOST") ?? "vapor.prelub.com" | |||||
| let envHost = Environment.get("DATABASE_HOST") ?? "127.0.0.1" | |||||
| var envPort: Int | |||||
| switch app.environment { | |||||
| case .development: | |||||
| envPort = 5433 | |||||
| default: | |||||
| envPort = 5432 | |||||
| } | |||||
| let envUsername = Environment.get("DATABASE_USERNAME") ?? "mcarman" | |||||
| let envPassword = Environment.get("DATABASE_PASSWORD") ?? "@ng31F@rm0823262" | |||||
| let envDB = Environment.get("DATABASE_NAME") ?? "fueling" | |||||
| app.databases.use(DatabaseConfigurationFactory.postgres(configuration: .init( | |||||
| hostname: envHost, | |||||
| port: envPort, | |||||
| username: envUsername, | |||||
| password: envPassword, | |||||
| database: envDB) | |||||
| ), as: .psql) | |||||
| // app.databases.use(.postgres(hostname: "127.0.0.1", port: 5432, username: "mcarman", password: "@ng31F@rm0823262", database: "fueling"), as: .psql) | |||||
| app.http.server.configuration.port = 3002 | |||||
| app.migrations.add(CreateFuelEntry()) | app.migrations.add(CreateFuelEntry()) | ||||
| app.migrations.add(AddLatitudeAndLongitudeAndRemoveGpsLocationAndImage()) | app.migrations.add(AddLatitudeAndLongitudeAndRemoveGpsLocationAndImage()) | ||||
| app.migrations.add(AddImageBackIn()) | |||||
| app.migrations.add(StoreImageDataInDatabase()) | |||||
| app.migrations.add(AddDownloadedBool()) | |||||
| app.migrations.add(CreateUser()) | |||||
| app.migrations.add(CreateTokens()) | |||||
| app.migrations.add(UpdateUser1()) | |||||
| app.migrations.add(UpdateTokens1()) | |||||
| app.migrations.add(UpdateUser2_AddTokenfield()) | |||||
| app.migrations.add(UpdateUser3_RemoveTokenfield()) | |||||
| app.migrations.add(AddLoadDateAndPullDate()) | |||||
| app.migrations.add(AddDeviceSerial()) | |||||
| try await app.autoMigrate().get() | try await app.autoMigrate().get() | ||||
| // register routes | // register routes | ||||
| @@ -1,23 +1,18 @@ | |||||
| //import Fluent | |||||
| //import Vapor | |||||
| // | |||||
| //func routes(_ app: Application) throws { | |||||
| // app.get { req async in | |||||
| // "It works!" | |||||
| // } | |||||
| // | |||||
| // app.get("hello") { req async -> String in | |||||
| // "Hello, world!" | |||||
| // } | |||||
| // | |||||
| // try app.register(collection: TodoController()) | |||||
| //} | |||||
| import Vapor | import Vapor | ||||
| func routes(_ app: Application) throws { | func routes(_ app: Application) throws { | ||||
| app.routes.caseInsensitive = true | |||||
| app.routes.defaultMaxBodySize = "64mb"; | |||||
| let v2 = app.grouped("api").grouped("v2") | |||||
| try v2.register(collection: FuelEntryV2Controller()) | |||||
| try v2.register(collection: UserController()) | |||||
| let fuelEntryController = FuelEntryController() | let fuelEntryController = FuelEntryController() | ||||
| app.get { | |||||
| req in return "Fueling API v2" | |||||
| } | |||||
| app.get("fuel-entries") { req in | app.get("fuel-entries") { req in | ||||
| try await fuelEntryController.index(req: req) | try await fuelEntryController.index(req: req) | ||||
| } | } | ||||
| @@ -29,6 +24,36 @@ func routes(_ app: Application) throws { | |||||
| app.delete("fuel-entries", ":fuelEntryID") { req in | app.delete("fuel-entries", ":fuelEntryID") { req in | ||||
| try await fuelEntryController.delete(req: req) | try await fuelEntryController.delete(req: req) | ||||
| } | } | ||||
| // V2 API routes with image upload | |||||
| // let v2Routes: () = app.group("api", "v2") { v2 in | |||||
| // let fuelEntryV2Controller = FuelEntryV2Controller() | |||||
| // | |||||
| // v2.get("fuel-entries") {req in | |||||
| // try await fuelEntryV2Controller.index(req: req) | |||||
| // } | |||||
| // | |||||
| //// v2.get("fuel-entries", "with-images") {req in | |||||
| //// try await fuelEntryV2Controller.getAllEntriesWithImages(req: req) | |||||
| //// } | |||||
| // | |||||
| // v2.post("fuel-entries") {req in | |||||
| // try await fuelEntryV2Controller.create(req: req) | |||||
| // } | |||||
| // | |||||
| //// v2.post("fuel-entries", ":id", "upload-image") { req -> FuelEntry in | |||||
| //// return try await fuelEntryV2Controller.uploadImage(req: req) | |||||
| //// } | |||||
| // | |||||
| // v2.delete("fuel-entries", ":fuelEntryID") { req in | |||||
| // try await fuelEntryV2Controller.delete(req: req) | |||||
| // } | |||||
| // } | |||||
| } | } | ||||