Ver a proveniência

Pushing edits

v2_uploadImages
rpm-mcarman há 1 ano
ascendente
cometimento
f202dff478
13 ficheiros alterados com 468 adições e 29 eliminações
  1. BIN
      .swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate
  2. +6
    -0
      .swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  3. +85
    -0
      Sources/App/Controllers/FuelEntryV2Controller.swift
  4. +75
    -1
      Sources/App/Controllers/UserController.swift
  5. +23
    -0
      Sources/App/Errors/UserErrors.swift
  6. +14
    -0
      Sources/App/Migrations/CreateFuelEntry.swift
  7. +23
    -0
      Sources/App/Migrations/CreateToken.swift
  8. +19
    -0
      Sources/App/Migrations/CreateUser.swift
  9. +75
    -1
      Sources/App/Models/FuelEntry.swift
  10. +53
    -1
      Sources/App/Models/Token.swift
  11. +61
    -5
      Sources/App/Models/User.swift
  12. +4
    -0
      Sources/App/configure.swift
  13. +30
    -21
      Sources/App/routes.swift

BIN
.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate Ver ficheiro


+ 6
- 0
.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist Ver ficheiro

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "D3C706B5-9CBD-4F91-8A68-CF08562866B5"
type = "1"
version = "2.0">
</Bucket>

+ 85
- 0
Sources/App/Controllers/FuelEntryV2Controller.swift Ver ficheiro

@@ -12,6 +12,7 @@ struct FuelEntryV2Controller: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let fuelEntries = routes.grouped("fuel_entries")
fuelEntries.get(use: index)
fuelEntries.get("test", use: filtered)
// fuelEntries.get("with-images", use: getAllEntriesWithImages)
fuelEntries.post(use: create)
// fuelEntries.post("upload-image", use: uploadImage)
@@ -24,6 +25,90 @@ struct FuelEntryV2Controller: RouteCollection {
return try await FuelEntry.query(on: req.db).all()
}
struct filteredreturn: Content{
var fuelentries: [FuelEntry]
var pages: Int
}
@Sendable
func filtered(req: Request) async throws -> Response {
var image: Bool
var serial: String
var tank: String
let qstring = req.query
if let qimage = try? qstring.get(Bool.self, at: "image"){
image = qimage
} else {
image = false
}
if let qserial = try? qstring.get(String.self, at: "serial"){
serial = qserial
} else {
serial = ""
}
if let qtank = try? qstring.get(String.self, at: "tank"){
tank = qtank
} else {
tank = ""
}
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)
} 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)
}
if(serial.count > 0){
query = query
.filter(\.$serialNumber == serial)
}
if(tank.count > 0){
query = query
.filter(\.$tankID == tank)
}
if(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, longitutde: 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!)
}
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, longitutde: 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)
}
return try await woimage.encodeResponse(for: req)
}
// let result = try await query.paginate(for: req)
// return try await result.encodeResponse(for: req)
}
// func getAllEntriesWithImages(req: Request) async throws -> [FuelEntry] {
// let fuelEntries = try await FuelEntry.query(on: req.db).all()
//


+ 75
- 1
Sources/App/Controllers/UserController.swift Ver ficheiro

@@ -5,4 +5,78 @@
// Created by Michiel Carman on 9/26/24.
//

import Foundation
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: RoutesBuilder) throws {
let usersRoute = routes.grouped("users")
usersRoute.post("signup", use: create)
let tokenProtected = usersRoute.grouped(Token.authenticator())
tokenProtected.get("me", use: getMyOwnUser)
let passwordProtected = usersRoute.grouped(User.authenticator())
passwordProtected.post("login", use: login)
}

fileprivate func create(req: Request) throws -> EventLoopFuture<NewSession> {
try UserSignup.validate(content: req)
let userSignup = try req.content.decode(UserSignup.self)
let user = try User.create(from: userSignup)
var token: Token!

return checkIfUserExists(userSignup.username, req: req).flatMap { exists in
guard !exists else {
return req.eventLoop.future(error: UserError.usernameTaken)
}

return user.save(on: req.db)
}.flatMap {
guard let newToken = try? user.createToken(source: .signup) else {
return req.eventLoop.future(error: Abort(.internalServerError))
}
token = newToken
return token.save(on: req.db)
}.flatMapThrowing {
NewSession(token: token.value, user: try user.asPublic())
}
}

fileprivate func login(req: Request) throws -> EventLoopFuture<NewSession> {
let user = try req.auth.require(User.self)
let token = try user.createToken(source: .login)

return token.save(on: req.db).flatMapThrowing {
NewSession(token: token.value, user: try user.asPublic())
}
}

func getMyOwnUser(req: Request) throws -> User.Public {
try req.auth.require(User.self).asPublic()
}

private func checkIfUserExists(_ username: String, req: Request) -> EventLoopFuture<Bool> {
User.query(on: req.db)
.filter(\.$username == username)
.first()
.map { $0 != nil }
}
}

+ 23
- 0
Sources/App/Errors/UserErrors.swift Ver ficheiro

@@ -6,3 +6,26 @@
//

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

+ 14
- 0
Sources/App/Migrations/CreateFuelEntry.swift Ver ficheiro

@@ -82,3 +82,17 @@ struct StoreImageDataInDatabase: AsyncMigration {
}
}

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


+ 23
- 0
Sources/App/Migrations/CreateToken.swift Ver ficheiro

@@ -6,3 +6,26 @@
//

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

+ 19
- 0
Sources/App/Migrations/CreateUser.swift Ver ficheiro

@@ -6,3 +6,22 @@
//

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

+ 75
- 1
Sources/App/Models/FuelEntry.swift Ver ficheiro

@@ -10,6 +10,39 @@ 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 longitutde: 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
}
struct wimage: Content{
let id: UUID?
let timestamp: Date
let userID: String
let latitude: Double
let longitutde: 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?
}

@ID(key: .id)
var id: UUID?
@@ -53,17 +86,58 @@ final class FuelEntry: Model, Content {
@Field(key: "is_override")
var isOverride: Bool
@Field(key: "downloaded")
var downloaded: Bool?
// var imageData: Data?

init() {}

init(id: UUID? = nil, timestamp: Date, userID: String, latitude: Double, longitude: Double, image: Data?, product: String, make: String, model: String, serialNumber: String, tankID: String, fuelAmount: Double, fuelType: String, isOverride: Bool, downloaded: Bool?) { //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.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) { //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
self.serialNumber = serialNumber
self.tankID = tankID
self.fuelAmount = fuelAmount
self.fuelType = fuelType
self.isOverride = isOverride
self.downloaded = downloaded ?? nil
self.image = image ?? 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) { //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


+ 53
- 1
Sources/App/Models/Token.swift Ver ficheiro

@@ -5,4 +5,56 @@
// Created by Michiel Carman on 9/24/24.
//

import Foundation
import Vapor
import Fluent

enum SessionSource: Int, Content {
case signup
case login
}

final class Token: Model {
static let schema = "token"
@ID()
var id: UUID?
@Parent(key: "user_id")
var user: User
@Field(key: "value")
var value: String
@Field(key: "source")
var source: SessionSource
@Field(key: "expires_at")
var expiresAt: Date?
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
init() {}
init(id: UUID? = nil, userId: User.IDValue, token: String,
source: SessionSource, expiresAt: Date?) {
self.id = id
self.$user.id = userId
self.value = token
self.source = source
self.expiresAt = expiresAt
}
}

extension Token: ModelTokenAuthenticatable {
static let valueKey = \Token.$value
static let userKey = \Token.$user
var isValid: Bool {
guard let expiryDate = expiresAt else {
return true
}
return expiryDate > Date()
}
}

+ 61
- 5
Sources/App/Models/User.swift Ver ficheiro

@@ -5,12 +5,68 @@
// Created by Michiel Carman on 9/24/24.
//

import Foundation
import Vapor
import Fluent
import Vapor

final class User: Model {
@ID var id: UUID?
@Field(key: "name") var name: String
struct Public: Content {
let username: String
let id: UUID
let createdAt: Date?
let updatedAt: Date?
}
static let schema = "user"
@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 {
static func create(from userSignup: UserSignup) throws -> User {
User(username: userSignup.username, passwordHash: try Bcrypt.hash(userSignup.password))
}
func createToken(source: SessionSource) throws -> Token {
let calendar = Calendar(identifier: .gregorian)
let expiryDate = calendar.date(byAdding: .year, value: 1, to: Date())
return try Token(userId: requireID(),
token: [UInt8].random(count: 16).base64, source: source, expiresAt: expiryDate)
}

func asPublic() throws -> Public {
Public(username: username,
id: try requireID(),
createdAt: createdAt,
updatedAt: updatedAt)
}
}

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

+ 4
- 0
Sources/App/configure.swift Ver ficheiro

@@ -4,6 +4,7 @@ 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 = "128mb"
@@ -15,6 +16,9 @@ public func configure(_ app: Application) async throws {
app.migrations.add(AddLatitudeAndLongitudeAndRemoveGpsLocationAndImage())
app.migrations.add(AddImageBackIn())
app.migrations.add(StoreImageDataInDatabase())
app.migrations.add(AddDownloadedBool())
app.migrations.add(CreateUser())
app.migrations.add(CreateTokens())
try await app.autoMigrate().get()

// register routes


+ 30
- 21
Sources/App/routes.swift Ver ficheiro

@@ -1,6 +1,12 @@
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("fuel-entries") { req in
@@ -15,30 +21,33 @@ func routes(_ app: Application) throws {
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)
// let v2Routes: () = app.group("api", "v2") { v2 in
// let fuelEntryV2Controller = FuelEntryV2Controller()
//
// v2.get("fuel-entries") {req in
// try await fuelEntryV2Controller.index(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.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.delete("fuel-entries", ":fuelEntryID") { req in
try await fuelEntryV2Controller.delete(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)
// }
// }
}



Carregando…
Cancelar
Guardar