Skip to content

Instantly share code, notes, and snippets.

@0xTim
Last active February 13, 2019 01:52
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save 0xTim/b372f91c8bd71ea757d1da526058f2d9 to your computer and use it in GitHub Desktop.
Save 0xTim/b372f91c8bd71ea757d1da526058f2d9 to your computer and use it in GitHub Desktop.
Changes for the Ray Wenderlich Vapor Videos for Vapor 3 RC 2

Vapor 3 RC 2 Updates

Vapor 3 RC 2 is out! To update, ensure that your Package.swift contains something like:

.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc"),

Depending on your dependenices - make sure that you use the "X.X.X-rc" - that's the important bit.

You will also need to update your toolbox - Vapor 3 RC2 now requires LibreSSL for macOS with NIO - you can either upgrade you toolbox (brew upgrade vapor) or just install LibreSSL (brew install libressl)

Section 1

Video 4 - Accepting Data

The await(on:) function has now been deprecated, and was always a stopgap. To accept data before learning about Futures you can use the POST helper function:

router.post(InfoData.self, at: "info") { req, data -> InfoResponse in
  return InfoResponse(request: data)
}

Video 5 - Challenge Your Own Routes

As above, use the new function for your POST route:

router.post(UserInfoData.self, at: "user-info") { req, userInfo -> String in
  return "Hello \(userInfo.name), you are \(userInfo.age)!"
}

Section 2

Video 3 - Controllers and CRUD

Remove the await(on:) call in AcronymsController.swift. You can now call save(on:) directly on a Future<Model>:

func createHandler(_ req: Request) throws -> Future<Acronym> {
  let acronym = try req.content.decode(Acronym.self)
  return acronym.save(on: req)
}

Video 5 - Challenge Users and Categories

As for Video 3, in CategoriesController.swift you should now have:

func createHandler(_ req: Request) throws -> Future<Category> {
  let category = try req.content.decode(Category.self)
  return category.save(on: req)
}

Video 6 - Parent Child Relationships

Fluent's get(on:) query now throws so the getCreatorHandler(_:) in AcronymsController.swift now looks like:

func getCreatorHandler(_ req: Request) throws -> Future<User> {
  return try req.parameter(Acronym.self).flatMap(to: User.self) { acronym in
  return try acronym.creator.get(on: req)
}

Video 8 - Fluent Queries

Fluent's group(_:) and filter(_:) queries throw so the searchHandler(_:) in AcronymsController.swift now looks like:

func searchHandler(_ req: Request) throws -> Future<[Acronym]> {
  guard let searchTerm = req.query[String.self, at: "term"] else {
    throw Abort(.badRequest)
  }
  return try Acronym.query(on: req).group(.or) { or in
    try or.filter(\.short == searchTerm)
    try or.filter(\.long == searchTerm)
    }.all()
}

Video 9 - Persisting Data with MySQL

The configuration for MySQLDatabase is now provided in a separate type. In configure.swift it should now look like:

var databases = DatabaseConfig()
let mysqlConfig = MySQLDatabaseConfig(hostname: "localhost", port: 3306, username: "til", password: "password", database: "vapor")
let database = MySQLDatabase(config: mysqlConfig)
databases.add(database: database, as: .mysql)
services.register(databases)

Section 3

Video 3 - Powerful Templates

As Fluent's get(on:) query now throws, the acronymHandler(_:) now looks like:

func acronymHandler(_ req: Request) throws -> Future<View> {
  return try req.parameter(Acronym.self).flatMap(to: View.self) { acronym in
    return try acronym.creator.get(on: req).flatMap(to: View.self) { creator in
      let context = AcronymContext(title: acronym.long, acronym: acronym, creator: creator)
      return try req.leaf().render("acronym", context)
    }
  }
}

Section 4

Video 2 - Passwords

Crypto's Random functions have been tweaked. The Token extension to generate a token should now look like:

extension Token {
  static func generate(for user: User) throws -> Token {
    let random = try CryptoRandom().generateData(count: 16)
    return try Token(token: random.base64EncodedString(), userID: user.requireID())
  }
}

BCrypt functions have been renamed as well. In your UsersController you'll need to import Crypto and change your createHandler(_:) should look like:

func createHandler(_ req: Request) throws -> Future<User> {
  return try req.content.decode(User.self).flatMap(to: User.self) { user in
    let hasher = try req.make(BCryptDigest.self)
    user.password = try String.convertFromData(hasher.hash(user.password))
    return user.save(on: req)
  }
}

Video 3 - API Authentication

When creating the basicAuthMiddleware you need to rename the Verifier. Your line should look like:

let basicAuthMiddleware = User.basicAuthMiddleware(using: BCryptDigest())

Video 4 - Website Authentication

When creating the verifier BCrypt types have been renamed:

let verifier = try req.make(BCryptDigest.self)
@frivas
Copy link

frivas commented Apr 23, 2018

Hello *,

Just a few comments to keep in mind when working with more recent versions of Vapor and some of the other components such as Crypto and Leaf.

Notes:

  • There is a new version of Crypto (3.1.1) released on Friday, April 26th.
  • Remember to import Crypto when using BCryptDigest()
  1. There are a couple of changes to be done in Package.swift. Notice Auth and Crypto had been added as packages and also Authentication, Crypto and Random as dependencies.
....
        .package(url: "https://github.com/vapor/auth.git", from: "2.0.0-rc"),
        .package(url: "https://github.com/vapor/crypto.git", from: "3.1.1")
    ],
    targets: [
        .target(name: "App", dependencies: ["FluentMySQL", "Vapor", "Leaf", "Authentication", "Crypto", "Random"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)
  1. Run "vapor update"
  2. Also the parameter method of Request is now deprecated. Now req.parameters.next() is used.
  3. At Section 2 - Video 8, notice you can add a reason to the Abort() function at AcronymsController.swift:
  func searchHandler(_ req: Request) throws -> Future<[Acronym]> {
    guard let searchTerm = req.query[String.self, at: "term"] else {
      throw Abort(.badRequest, reason: "Missing search term in request")
    }
    return try Acronym.query(on: req).group(.or) { or in
      try or.filter(\.short == searchTerm)
      try or.filter(\.long == searchTerm)
    }.all()
  }
  1. In the new version of Crypto the method hash does return a String
    public func hash(_ plaintext: LosslessDataConvertible, cost: Int = 12, salt: LosslessDataConvertible? = nil) throws -> String {...
    so there is no need to use String.convertFromData() the resulting function at UsersController.swift would look like:
  func createHandler(_ req: Request) throws -> Future<User> {
    return try req.content.decode(User.self).flatMap(to: User.self) { user in
      let hasher = try req.make(BCryptDigest.self)
      user.password = try hasher.hash(user.password)
      return user.save(on: req)
    }
  }
  1. In configure.swift DatabaseConfig() has been renamed to DatabasesConfig()

I hope you all find these comments useful.

Thank you very much

@vinamelody
Copy link

Dang! I still got error and forgot to import Crypto

@Chrichton
Copy link

I think the code for Section 1 / Video 4 should be:

router.post(UserInfo.self, at: "user-info") { req, userInfo in
return "Hello (userInfo.name) you are (userInfo.age)"
}

as we are only to return a String.
Your solution returns a JSON, which is also nice.
Thanks for your gist, as I watched to video and use Vapor 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment