Confronta commit

...

37 Commit

Autore SHA1 Messaggio Data
  Mike Carman 96ca775eb2 Fix for useremail 2 mesi fa
  Mike Carman 745b9971f0 Fueling change to allow for update and create. 5 mesi fa
  Mike Carman 75284b1afe Changed tableserial to useremail 1 anno fa
  Mike Carman 87eac42ba9 Added tabletserial to database and responses 1 anno fa
  Mike Carman d651f365a4 db connection 1 anno fa
  Mike Carman 4216bbf96c Maxim qstrings and other data 1 anno fa
  Mike Carman ce895f2712 Fixed image always null 1 anno fa
  Mike Carman aafba8cb9b Connection String Port Number update 1 anno fa
  Mike Carman a3e809a74b All fueling changes requested for apis 1 anno fa
  Mike Carman cade0e4f23 useriterface 1 anno fa
  Mike Carman 5f0204d89c database port 1 anno fa
  Mike Carman 7a66dc7ed3 Changes for Andy 1 anno fa
  Mike Carman 976f328f37 small modification to rpm query 1 anno fa
  Mike Carman ad33c6ce24 Fixed db connection string 1 anno fa
  Mike Carman e1d679bf03 Fueling service changes per Andy added startdate and enddate perameter as well as previous parameter 1 anno fa
  Mike Carman c586708c1c Reset port 1 anno fa
  Mike Carman ee94988698 fix for null downloaded 1 anno fa
  Mike Carman 5ecff22dbf Changed port on connection string 1 anno fa
  Mike Carman 8539c28d4b latest 1 anno fa
  rpm-mcarman 5fd27fa971 Update for image field 1 anno fa
  rpm-mcarman e1c683d582 Token auth implementation 1 anno fa
  rpm-mcarman 3a257e2b4d Added to selected fields 1 anno fa
  rpm-mcarman da4e9a7324 Added downloaded field to data 1 anno fa
  rpm-mcarman f202dff478 Pushing edits 1 anno fa
  rpm-mcarman e6b23df180 w/o token auth on api calls 1 anno fa
  rpm-mcarman 7e31c3c36b Removed Pagination 2 anni fa
  Bill Ryckman 46d69a83dc Adding pagination to GET request 2 anni fa
  admin 3976bac4aa app.routes.defaultMaxBodySize = 128mb 2 anni fa
  admin 678340efac Remove processing of imageData to a file. Store imageData in image field in database. 2 anni fa
  admin 5e562aa8d8 Update routes to use api/v2 and include with-images 2 anni fa
  admin 64f9c4aac9 Second attempt at uploading images. This time the imageData will come with the JSON data and get processed during the Create. 2 anni fa
  admin d6a38d0e35 Remove Field imageData as it's not going to the database, just JSON. 2 anni fa
  admin 607135f873 Move back to Swift Tools 5.8 and move to port 3002. 2 anni fa
  admin 86d9ef808e Downgrade Swift Tools Package to 5.7.3 to work on Vapor server. 2 anni fa
  admin 934d4d9c25 Added getAllEntriesWithImages to bring down pictures with data if need be. Left default route intact without images. 2 anni fa
  admin b6402df273 Update to create new controller for V2 routes. 2 anni fa
  admin 9ccee49e92 First attempt at image upload. Need to set image = to WorkingDirectory+Images+UUID.png. Then upload id and image to complete process. 2 anni fa
21 ha cambiato i file con 1683 aggiunte e 110 eliminazioni
  1. BIN
      .swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate
  2. BIN
      .swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate
  3. +152
    -0
      .swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  4. +14
    -0
      .swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcschemes/xcschememanagement.plist
  5. +24
    -0
      .swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  6. +4
    -3
      Sources/App/Controllers/FuelEntryController.swift
  7. +797
    -0
      Sources/App/Controllers/FuelEntryV2Controller.swift
  8. +0
    -31
      Sources/App/Controllers/TodoController.swift
  9. +42
    -0
      Sources/App/Controllers/UserController.swift
  10. +31
    -0
      Sources/App/Errors/UserErrors.swift
  11. +48
    -0
      Sources/App/Helpers/PageHelper.swift
  12. +93
    -19
      Sources/App/Migrations/CreateFuelEntry.swift
  13. +0
    -14
      Sources/App/Migrations/CreateTodo.swift
  14. +51
    -0
      Sources/App/Migrations/CreateToken.swift
  15. +69
    -0
      Sources/App/Migrations/CreateUser.swift
  16. +162
    -4
      Sources/App/Models/FuelEntry.swift
  17. +0
    -19
      Sources/App/Models/Todo.swift
  18. +50
    -0
      Sources/App/Models/Token.swift
  19. +63
    -0
      Sources/App/Models/User.swift
  20. +43
    -5
      Sources/App/configure.swift
  21. +40
    -15
      Sources/App/routes.swift

BIN
.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate Vedi File


BIN
.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate Vedi File


+ 152
- 0
.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist Vedi File

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

+ 14
- 0
.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcschemes/xcschememanagement.plist Vedi File

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

+ 24
- 0
.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist Vedi File

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

+ 4
- 3
Sources/App/Controllers/FuelEntryController.swift Vedi File

@@ -1,6 +1,6 @@
//
// FuelEntryController.swift
//
//
//
// Created by Bill Ryckman on 4/24/23.
//
@@ -20,13 +20,13 @@ struct FuelEntryController: RouteCollection {
func index(req: Request) async throws -> [FuelEntry] {
try await FuelEntry.query(on: req.db).all()
}
func create(req: Request) async throws -> [FuelEntry] {
let fuelEntry = try req.content.decode([FuelEntry].self)
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)
@@ -39,3 +39,4 @@ struct FuelEntryController: RouteCollection {





+ 797
- 0
Sources/App/Controllers/FuelEntryV2Controller.swift Vedi File

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











+ 0
- 31
Sources/App/Controllers/TodoController.swift Vedi File

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

+ 42
- 0
Sources/App/Controllers/UserController.swift Vedi File

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

+ 31
- 0
Sources/App/Errors/UserErrors.swift Vedi File

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

+ 48
- 0
Sources/App/Helpers/PageHelper.swift Vedi File

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

+ 93
- 19
Sources/App/Migrations/CreateFuelEntry.swift Vedi File

@@ -1,6 +1,6 @@
//
// File.swift
//
//
//
// Created by Bill Ryckman on 4/26/23.
//
@@ -26,29 +26,103 @@ struct CreateFuelEntry: AsyncMigration {
.field("is_override", .bool)
.create()
}
func revert(on database: Database) async throws {
try await database.schema("fuel_entries").delete()
}
}

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


+ 0
- 14
Sources/App/Migrations/CreateTodo.swift Vedi File

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

+ 51
- 0
Sources/App/Migrations/CreateToken.swift Vedi File

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

+ 69
- 0
Sources/App/Migrations/CreateUser.swift Vedi File

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

+ 162
- 4
Sources/App/Models/FuelEntry.swift Vedi File

@@ -10,6 +10,111 @@ import Vapor

final class FuelEntry: Model, Content {
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)
var id: UUID?
@@ -26,8 +131,8 @@ final class FuelEntry: Model, Content {
@Field(key: "longitude")
var longitude: Double

// @Field(key: "image")
// var image: String
@Field(key: "image")
var image: Data?

@Field(key: "product")
var product: String
@@ -52,16 +157,67 @@ final class FuelEntry: Model, Content {

@Field(key: "is_override")
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(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.timestamp = timestamp
self.userID = userID
self.latitude = latitude
self.longitude = longitude
// self.image = image
self.image = image ?? nil
self.product = product
self.make = make
self.model = model
@@ -70,5 +226,7 @@ final class FuelEntry: Model, Content {
self.fuelAmount = fuelAmount
self.fuelType = fuelType
self.isOverride = isOverride
self.tabletserial = tabletserial ?? nil
// self.imageData = imageData
}
}

+ 0
- 19
Sources/App/Models/Todo.swift Vedi File

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

+ 50
- 0
Sources/App/Models/Token.swift Vedi File

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

+ 63
- 0
Sources/App/Models/User.swift Vedi File

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






+ 43
- 5
Sources/App/configure.swift Vedi File

@@ -1,18 +1,56 @@
import Fluent
import FluentPostgresDriver
import NIOSSL

import Vapor

// configures your application
public func configure(_ app: Application) async throws {
app.middleware.use(ErrorMiddleware.default(environment: app.environment))
// uncomment to serve files from /Public folder
// 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(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()

// register routes


+ 40
- 15
Sources/App/routes.swift Vedi File

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

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()
app.get {
req in return "Fueling API v2"
}
app.get("fuel-entries") { req in
try await fuelEntryController.index(req: req)
}
@@ -29,6 +24,36 @@ func routes(_ app: Application) throws {
app.delete("fuel-entries", ":fuelEntryID") { req in
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)
// }
// }
}



Caricamento…
Annulla
Salva