比较提交

...

37 次代码提交

作者 SHA1 备注 提交日期
  Mike Carman 96ca775eb2 Fix for useremail 2 个月前
  Mike Carman 745b9971f0 Fueling change to allow for update and create. 5 个月前
  Mike Carman 75284b1afe Changed tableserial to useremail 1年前
  Mike Carman 87eac42ba9 Added tabletserial to database and responses 1年前
  Mike Carman d651f365a4 db connection 1年前
  Mike Carman 4216bbf96c Maxim qstrings and other data 1年前
  Mike Carman ce895f2712 Fixed image always null 1年前
  Mike Carman aafba8cb9b Connection String Port Number update 1年前
  Mike Carman a3e809a74b All fueling changes requested for apis 1年前
  Mike Carman cade0e4f23 useriterface 1年前
  Mike Carman 5f0204d89c database port 1年前
  Mike Carman 7a66dc7ed3 Changes for Andy 1年前
  Mike Carman 976f328f37 small modification to rpm query 1年前
  Mike Carman ad33c6ce24 Fixed db connection string 1年前
  Mike Carman e1d679bf03 Fueling service changes per Andy added startdate and enddate perameter as well as previous parameter 1年前
  Mike Carman c586708c1c Reset port 1年前
  Mike Carman ee94988698 fix for null downloaded 1年前
  Mike Carman 5ecff22dbf Changed port on connection string 1年前
  Mike Carman 8539c28d4b latest 1年前
  rpm-mcarman 5fd27fa971 Update for image field 1年前
  rpm-mcarman e1c683d582 Token auth implementation 1年前
  rpm-mcarman 3a257e2b4d Added to selected fields 1年前
  rpm-mcarman da4e9a7324 Added downloaded field to data 1年前
  rpm-mcarman f202dff478 Pushing edits 1年前
  rpm-mcarman e6b23df180 w/o token auth on api calls 1年前
  rpm-mcarman 7e31c3c36b Removed Pagination 2 年前
  Bill Ryckman 46d69a83dc Adding pagination to GET request 2 年前
  admin 3976bac4aa app.routes.defaultMaxBodySize = 128mb 2 年前
  admin 678340efac Remove processing of imageData to a file. Store imageData in image field in database. 2 年前
  admin 5e562aa8d8 Update routes to use api/v2 and include with-images 2 年前
  admin 64f9c4aac9 Second attempt at uploading images. This time the imageData will come with the JSON data and get processed during the Create. 2 年前
  admin d6a38d0e35 Remove Field imageData as it's not going to the database, just JSON. 2 年前
  admin 607135f873 Move back to Swift Tools 5.8 and move to port 3002. 2 年前
  admin 86d9ef808e Downgrade Swift Tools Package to 5.7.3 to work on Vapor server. 2 年前
  admin 934d4d9c25 Added getAllEntriesWithImages to bring down pictures with data if need be. Left default route intact without images. 2 年前
  admin b6402df273 Update to create new controller for V2 routes. 2 年前
  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 年前
共有 21 个文件被更改,包括 1683 次插入110 次删除
  1. 二进制
      .swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate
  2. 二进制
      .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

二进制
.swiftpm/xcode/package.xcworkspace/xcuserdata/mcarman.xcuserdatad/UserInterfaceState.xcuserstate 查看文件


二进制
.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate 查看文件


+ 152
- 0
.swiftpm/xcode/xcuserdata/mcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

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



正在加载...
取消
保存