Переглянути джерело

Vapor 2 (#2828)

* Updated Swift from version 3.0.2 to version 3.1.1

* Updated to Vapor 2

* Updated TfbCommon files

* Updated MongoDb

* Updated MySQL

* Updated PostgreSQL

* Pinned SPM

* Renamed servers.json to server.json

* Removed unused module

* Added ctls and cmysql .sh files. Added them to setup-*.sh in vapor

* Added more middleware

* Added correct providers

* Changed to URL

* Fluent and database fix

* Removed preparation

* ContentMiddleware fixes

* Updated to fluent provider 1.1.0

* Tests pass now

* Postgresql typo fixes

* Removed some warings
Kim de Vos 8 роки тому
батько
коміт
1320ec1c0c
42 змінених файлів з 1252 додано та 569 видалено
  1. 12 10
      frameworks/Swift/vapor/Config/crypto.json
  2. 11 11
      frameworks/Swift/vapor/Config/droplet.json
  3. 30 0
      frameworks/Swift/vapor/Config/fluent.json
  4. 1 5
      frameworks/Swift/vapor/Config/secrets/mongo.json
  5. 12 0
      frameworks/Swift/vapor/Config/server.json
  6. 0 7
      frameworks/Swift/vapor/Config/servers.json
  7. 228 0
      frameworks/Swift/vapor/Package.pins
  8. 5 4
      frameworks/Swift/vapor/Package.swift
  9. 19 0
      frameworks/Swift/vapor/Sources/TfbCommon/ContentMiddleware.swift
  10. 14 14
      frameworks/Swift/vapor/Sources/TfbCommon/Message.swift
  11. 8 8
      frameworks/Swift/vapor/Sources/TfbCommon/ServerMiddleware.swift
  12. 3 3
      frameworks/Swift/vapor/Sources/TfbCommon/World.swift
  13. 30 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Config+Setup.swift
  14. 10 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Droplet+Setup.swift
  15. 0 58
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Fortune.swift
  16. 82 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Models/Fortune.swift
  17. 77 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Models/World.swift
  18. 72 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Routes.swift
  19. 0 52
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/World.swift
  20. 25 79
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/main.swift
  21. 30 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Config+Setup.swift
  22. 10 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Droplet+Setup.swift
  23. 0 42
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Fortune.swift
  24. 78 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift
  25. 79 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift
  26. 73 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Routes.swift
  27. 0 35
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/World.swift
  28. 25 80
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift
  29. 30 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Config+Setup.swift
  30. 10 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Droplet+Setup.swift
  31. 0 42
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Fortune.swift
  32. 74 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Models/Fortune.swift
  33. 74 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Models/World.swift
  34. 72 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Routes.swift
  35. 0 35
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/World.swift
  36. 25 79
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/main.swift
  37. 1 1
      frameworks/Swift/vapor/setup-mongodb.sh
  38. 1 1
      frameworks/Swift/vapor/setup-mysql.sh
  39. 1 1
      frameworks/Swift/vapor/setup-postgresql.sh
  40. 2 2
      toolset/setup/linux/languages/swift3.sh
  41. 14 0
      toolset/setup/linux/systools/cmysql.sh
  42. 14 0
      toolset/setup/linux/systools/ctls.sh

+ 12 - 10
frameworks/Swift/vapor/Config/crypto.json

@@ -1,11 +1,13 @@
 {
-  "hash": {
-    "method": "sha256",
-    "key": "password"
-  },
-  "cipher": {
-    "method": "chacha20",
-    "key": "passwordpasswordpasswordpassword",
-    "iv": "password"
-  }
-}
+    "hash": {
+        "method": "sha256",
+        "encoding": "hex",
+        "key": "RyVibi7v2YKv87LCSlnUi8vYZrCMFPVIz82XrJtnpeM="
+    },
+
+    "cipher": {
+        "method": "aes256",
+        "encoding": "base64",
+        "key": "gIW4/8F6uQcKioBXiJfK3N6bGeVYs7Znf9Njtzs6jeQ="
+    }
+}

+ 11 - 11
frameworks/Swift/vapor/Config/droplet.json

@@ -1,14 +1,14 @@
 {
-  "server": "engine",
-  "client": "engine",
-  "console": "terminal",
-  "log": "console",
-  "hash": "crypto",
-  "cipher": "crypto",
-  "middleware": {
-    "server": [
-      "date"
+    "server": "engine",
+    "client": "engine",
+    "console": "terminal",
+    "log": "console",
+    "hash": "crypto",
+    "cipher": "crypto",
+    "middleware": [
+        "date",
+        "server",
+        "content-header"
     ],
-    "client": []
-  }
+    "view": "leaf",
 }

+ 30 - 0
frameworks/Swift/vapor/Config/fluent.json

@@ -0,0 +1,30 @@
+{
+    "//": "The underlying database technology to use.",
+    "//": "memory: SQLite in-memory DB.",
+    "//": "sqlite: Persisted SQLite DB (configure with sqlite.json)",
+    "//": "Other drivers are available through Vapor providers",
+    "//": "https://github.com/search?q=topic:vapor-provider+topic:database",
+    "driver": "memory",
+    
+    "//": "Naming convention to use for creating foreign id keys,",
+    "//": "e.g., `user_id`",
+    "//": "`camelCase` option is also available.",
+    "keyNamingConvention": "snake_case",
+    
+    "//": "Name of the table Fluent uses to track migrations",
+    "migrationEntityName": null,
+    
+    "//": "Character used to join pivot tables, e.g., `user_pet`",
+    "pivotNameConnector": "_",
+    
+    "//": "If true, foreign keys will automatically be added",
+    "//": "to any `builder.foreignId(...)` calls.",
+    "autoForeignKeys": true,
+    
+    "//": "Key to specify page number for paginated responses",
+    "//": "e.g., `?page=2` ",
+    "defaultPageKey": "page",
+    
+    "//": "Default page size if not otherwise specified on models",
+    "defaultPageSize": 10
+}

+ 1 - 5
frameworks/Swift/vapor/Config/secrets/mongo.json

@@ -1,7 +1,3 @@
 {
-  "user": "",
-  "password": "",
-  "database": "hello_world",
-  "port": "27017",
-  "host": "TFB-database"
+    "url": "mongodb://TFB-database:27017/hello_world"
 }

+ 12 - 0
frameworks/Swift/vapor/Config/server.json

@@ -0,0 +1,12 @@
+{
+    "//": "The $PORT:8080 call tells the json file to see if there",
+    "//": "is any value at the 'PORT' environment variable.",
+    "//": "If there is no value there, it will fallback to '8080'",
+    "port": "$PORT:8080",
+    
+    "host": "0.0.0.0",
+    
+    "//": "It's very rare that a server manages its own TLS.",
+    "//": "More commonly, vapor is served behind a proxy like nginx.",
+    "securityLayer": "none"
+}

+ 0 - 7
frameworks/Swift/vapor/Config/servers.json

@@ -1,7 +0,0 @@
-{
-  "default": {
-    "port": "$PORT:8080",
-    "host": "0.0.0.0",
-    "securityLayer": "none"
-  }
-}

+ 228 - 0
frameworks/Swift/vapor/Package.pins

@@ -0,0 +1,228 @@
+{
+  "autoPin": true,
+  "pins": [
+    {
+      "package": "BCrypt",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/bcrypt.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "BSON",
+      "reason": null,
+      "repositoryURL": "https://github.com/OpenKitten/BSON.git",
+      "version": "5.0.5"
+    },
+    {
+      "package": "Bits",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/bits.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "CMySQL",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/cmysql.git",
+      "version": "2.0.2"
+    },
+    {
+      "package": "CPostgreSQL",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor-community/cpostgresql.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "CTLS",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/ctls.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "Cheetah",
+      "reason": null,
+      "repositoryURL": "https://github.com/OpenKitten/Cheetah.git",
+      "version": "1.0.1"
+    },
+    {
+      "package": "Console",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/console.git",
+      "version": "2.1.0"
+    },
+    {
+      "package": "Core",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/core.git",
+      "version": "2.0.2"
+    },
+    {
+      "package": "Crypto",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/crypto.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "CryptoSwift",
+      "reason": null,
+      "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git",
+      "version": "0.6.9"
+    },
+    {
+      "package": "Debugging",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/debugging.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "Engine",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/engine.git",
+      "version": "2.0.4"
+    },
+    {
+      "package": "Fluent",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/fluent.git",
+      "version": "2.0.4"
+    },
+    {
+      "package": "FluentProvider",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/fluent-provider.git",
+      "version": "1.1.0"
+    },
+    {
+      "package": "JSON",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/json.git",
+      "version": "2.0.2"
+    },
+    {
+      "package": "KittenCore",
+      "reason": null,
+      "repositoryURL": "https://github.com/OpenKitten/KittenCore.git",
+      "version": "0.2.3"
+    },
+    {
+      "package": "Leaf",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/leaf.git",
+      "version": "2.0.1"
+    },
+    {
+      "package": "LeafProvider",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/leaf-provider.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "MongoDriver",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/mongo-driver.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "MongoKitten",
+      "reason": null,
+      "repositoryURL": "https://github.com/OpenKitten/MongoKitten.git",
+      "version": "4.0.9"
+    },
+    {
+      "package": "MongoProvider",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/mongo-provider.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "Multipart",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/multipart.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "MySQL",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/mysql.git",
+      "version": "2.0.1"
+    },
+    {
+      "package": "MySQLDriver",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/mysql-driver.git",
+      "version": "2.0.1"
+    },
+    {
+      "package": "MySQLProvider",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/mysql-provider.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "Node",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/node.git",
+      "version": "2.0.3"
+    },
+    {
+      "package": "PostgreSQL",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor-community/postgresql.git",
+      "version": "2.0.1"
+    },
+    {
+      "package": "PostgreSQLDriver",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/postgresql-driver.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "PostgreSQLProvider",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/postgresql-provider.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "Random",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/random.git",
+      "version": "1.0.0"
+    },
+    {
+      "package": "Routing",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/routing.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "SQLite",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/sqlite.git",
+      "version": "2.0.0"
+    },
+    {
+      "package": "Schrodinger",
+      "reason": null,
+      "repositoryURL": "https://github.com/OpenKitten/Schrodinger.git",
+      "version": "1.0.1"
+    },
+    {
+      "package": "Sockets",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/sockets.git",
+      "version": "2.0.1"
+    },
+    {
+      "package": "TLS",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/tls.git",
+      "version": "2.0.4"
+    },
+    {
+      "package": "Vapor",
+      "reason": null,
+      "repositoryURL": "https://github.com/vapor/vapor.git",
+      "version": "2.0.8"
+    }
+  ],
+  "version": 1
+}

+ 5 - 4
frameworks/Swift/vapor/Package.swift

@@ -8,10 +8,11 @@ let package = Package(
     Target(name: "vapor-tfb-mongodb", dependencies: ["TfbCommon"])
   ],
   dependencies: [
-    .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 5),
-    .Package(url: "https://github.com/vapor/postgresql-provider", majorVersion:1, minor: 1),
-    .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1, minor: 0),
-    .Package(url: "https://github.com/vapor/mongo-provider.git", majorVersion: 1, minor: 1)
+    .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2, minor: 0),
+    .Package(url: "https://github.com/vapor/postgresql-provider.git", majorVersion:2),
+    .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 2),
+    .Package(url: "https://github.com/vapor/mongo-provider.git", majorVersion: 2),
+    .Package(url: "https://github.com/vapor/leaf-provider.git", majorVersion: 1)
   ],
   exclude: [
     "Config",

+ 19 - 0
frameworks/Swift/vapor/Sources/TfbCommon/ContentMiddleware.swift

@@ -0,0 +1,19 @@
+import HTTP
+
+/// Middleware that adds `Server` HTTP header to response.
+public final class ContentMiddleware: Middleware {
+    
+    public init() { }
+    
+    public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
+        let response = try next.respond(to: request)
+
+        if response.headers[HeaderKey.contentType] == "text/plain; charset=utf-8" {
+            response.headers[HeaderKey.contentType] = "text/plain"
+        } else if response.headers[HeaderKey.contentType] == "application/json; charset=utf-8" {
+            response.headers[HeaderKey.contentType] = "application/json"
+        }
+        
+        return response
+    } 
+}

+ 14 - 14
frameworks/Swift/vapor/Sources/TfbCommon/Message.swift

@@ -1,21 +1,21 @@
 import Node
 
 public struct Message {
-  public let message: String
-  
-  public init(_ message: String) {
-    self.message = message
-  }
+    public let message: String
+
+    public init(_ message: String) {
+        self.message = message
+    }
 }
 
 extension Message: NodeConvertible {
-  public init(node: Node, in context: Context) throws {
-    message = try node.extract("message")
-  }
-  
-  public func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "message": message
-      ])
-  }
+    public init(node: Node) throws {
+        message = try node.get("message")
+    }
+
+    public func makeNode(in context: Context?) throws -> Node {
+        return try Node(node: [
+            "message": message
+            ])
+    }
 }

+ 8 - 8
frameworks/Swift/vapor/Sources/TfbCommon/ServerMiddleware.swift

@@ -2,14 +2,14 @@ import HTTP
 
 /// Middleware that adds `Server` HTTP header to response.
 public final class ServerMiddleware: Middleware {
-  
-  public init() { }
-  
-  public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
-    let response = try next.respond(to: request)
     
-    response.headers["Server"] = "Vapor"
+    public init() { }
     
-    return response
-  }
+    public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
+        let response = try next.respond(to: request)
+        
+        response.headers["Server"] = "Vapor"
+        
+        return response
+    } 
 }

+ 3 - 3
frameworks/Swift/vapor/Sources/TfbCommon/World.swift

@@ -1,10 +1,10 @@
-import Random
+import Crypto
 
 public struct WorldMeta {
   private init() { }
 
   public static let maxId: UInt32 = 10000
-  public static let randomId = { () -> UInt32 in UInt32(1) + CryptoRandom.uint32 % maxId }
+  public static let randomId = { () -> UInt32 in UInt32(1) + (try! Random.makeUInt32()) % maxId }
   public static let maxRandomNumber: Int32 = 10000
-  public static let randomRandomNumber = { () -> Int32 in Int32(1) + abs(CryptoRandom.int32) % maxRandomNumber }
+  public static let randomRandomNumber = { () -> Int32 in Int32(1) + abs(try! Random.makeInt32()) % maxRandomNumber }
 }

+ 30 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Config+Setup.swift

@@ -0,0 +1,30 @@
+import MongoProvider
+import TfbCommon
+import LeafProvider
+
+extension Config {
+    public func setup() throws {
+        // allow fuzzy conversions for these types
+        // (add your own types here)
+        Node.fuzzy = [Row.self, JSON.self, Node.self]
+
+        try setupProviders()
+        try setupPreparations()
+        
+        self.addConfigurable(middleware: ServerMiddleware(), name: "server")
+        self.addConfigurable(middleware: ContentMiddleware(), name: "content-header")
+    }
+    
+    /// Configure providers
+    private func setupProviders() throws {
+        try addProvider(MongoProvider.Provider.self)
+        try addProvider(LeafProvider.Provider.self)
+    }
+    
+    /// Add all models that should have their
+    /// schemas prepared before the app boots
+    private func setupPreparations() throws {
+        preparations.append(Fortune.self)
+        preparations.append(World.self)
+    }
+}

+ 10 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Droplet+Setup.swift

@@ -0,0 +1,10 @@
+@_exported import Vapor
+
+extension Droplet {
+    public func setup() throws {
+        try collection(Routes.self)
+
+        Fortune.database = try drop.assertDatabase()
+        World.database = try drop.assertDatabase()
+    }
+}

+ 0 - 58
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Fortune.swift

@@ -1,58 +0,0 @@
-import Vapor
-
-final class Fortune: Model {
-
-  static let entity = "fortune"
-
-  var mongoId: Node?
-  var id: Node?
-  var message: String
-
-  static var idKey = "_id"
-
-  // For internal Vapor use
-  var exists: Bool = false
-
-  init(id: Int, message: String) {
-    self.id = Node(id)
-    self.message = message
-  }
-
-  init(node: Node, in context: Context) throws {
-    id = try node.extract("_id")
-    mongoId = try node.extract("id")
-    message = try node.extract("message")
-  }
-
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": mongoId,
-      "_id": id,
-      "message": message
-    ])
-  }
-
-  func makeJSONNode() throws -> Node {
-    return try Node(node: [
-      "id": id?.int,
-      "message": message
-    ])
-  }
-
-  func makeJSON() throws -> JSON {
-    let node = try makeJSONNode()
-    return try JSON(node: node)
-  }
-
-  static func prepare(_ database: Database) throws {
-    try database.create("Fortune") { fortune in
-      fortune.id("id")
-      fortune.string("message")
-    }
-  }
-
-  static func revert(_ database: Database) throws {
-    try database.delete("Fortune")
-  }
-
-}

+ 82 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Models/Fortune.swift

@@ -0,0 +1,82 @@
+import Vapor
+import FluentProvider
+
+final class Fortune: Model {
+    static let entity = "fortune"
+
+    var storage: Storage = Storage()
+    var mongoId: Node?
+    var id: Node?
+    var message: String
+
+    static var idKey = "_id"
+
+    // For internal Vapor use
+    var exists: Bool = false
+
+    init(id: Int, message: String) {
+        self.id = Node(id)
+        self.message = message
+    }
+
+    init(node: Node, in context: Context) throws {
+        id = try node.get("_id")
+        mongoId = try node.get("id")
+        message = try node.get("message")
+    }
+
+    /// Initializes the Fortune from the
+    /// database row
+    init(row: Row) throws {
+        mongoId = try row.get("id")
+        id = try row.get("_id")
+        message  = try row.get("message")
+    }
+
+    // Serializes the Fortune to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", mongoId!)
+        try row.set("_id", id!)
+        try row.set("message", message)
+
+        return row
+    }
+
+    func makeNode(context: Context) throws -> Node {
+        return try Node(node: [
+            "id": mongoId!,
+            "_id": id!,
+            "message": message
+            ])
+    }
+
+    func makeJSONNode() throws -> Node {
+        return try Node(node: [
+            "id": id!.int!,
+            "message": message
+            ])
+    }
+
+    func makeJSON() throws -> JSON {
+        let node = try makeJSONNode()
+        return JSON(node: node)
+    }
+}
+
+
+
+// MARK: Fluent Preparation
+
+extension Fortune: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Fortunes
+    static func prepare(_ database: Database) throws {
+
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}

+ 77 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Models/World.swift

@@ -0,0 +1,77 @@
+import Vapor
+import FluentProvider
+
+final class World: Model {
+    static let entity = "world"
+    
+    let storage = Storage()
+
+    /// The content of the world
+    var mongoId: Node?
+    var id: Node?
+    var randomNumber: Int32
+
+    /// Creates a new World
+    init(_id: Node, mongoId: Node, randomNumber: Int32) {
+        self.id = _id
+        self.mongoId = mongoId
+        self.randomNumber = randomNumber
+    }
+
+    // MARK: Fluent Serialization
+
+    /// Initializes the World from the
+    /// database row
+    init(row: Row) throws {
+        mongoId = try row.get("id")
+        id = try row.get("_id")
+        randomNumber  = try row.get("randomNumber")
+    }
+
+    // Serializes the World to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", mongoId)
+//        try row.set("_id", id)
+        try row.set("randomNumber", randomNumber)
+
+        return row
+    }
+}
+
+// MARK: Fluent Preparation
+
+extension World: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Worlds
+    static func prepare(_ database: Database) throws {
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}
+
+// MARK: JSON
+extension World: JSONConvertible {
+    convenience init(json: JSON) throws {
+        try self.init(
+            _id: json.get("_id"),
+            mongoId: json.get("id"),
+            randomNumber: json.get("randomNumber")
+        )
+    }
+
+    func makeJSON() throws -> JSON {
+        var json = JSON()
+//        try json.set("_id", id)
+        try json.set("id", mongoId)
+        try json.set("randomNumber", randomNumber)
+        return json
+    }
+}
+
+// MARK: HTTP
+
+extension World: ResponseRepresentable { }

+ 72 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Routes.swift

@@ -0,0 +1,72 @@
+import Vapor
+import TfbCommon
+import Foundation
+
+final class Routes: RouteCollection {
+    func build(_ builder: RouteBuilder) throws {
+        builder.get("json") { req in
+            return try JSON(node: Message("Hello, World!"))
+        }
+
+        builder.get("plaintext") { req in
+            return "Hello, world!"
+        }
+
+        // response to requests to /info domain
+        // with a description of the request
+        builder.get("info") { req in
+            return req.description
+        }
+
+        builder.get("description") { req in return req.description }
+
+        // Test type 2: Single database query
+        builder.get("db") { _ in
+            let worldId = WorldMeta.randomId()
+            return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+        }
+
+        // Test type 3: Multiple database queries
+        builder.get("queries") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+            return JSON(worlds)
+        }
+
+        // Test type 4: Fortunes
+        let posixLocale = Locale(identifier: "en_US_POSIX")
+        builder.get("fortunes") { _ in
+            var fortunes = try Fortune.all()
+            let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+            fortunes.insert(additional, at: 0)
+            fortunes.sort(by: { lhs, rhs -> Bool in
+                return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+            })
+
+            let nodes = try fortunes.map { try $0.makeJSONNode() }
+            return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+        }
+
+        // Test type 5: Database updates
+        builder.get("updates") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            var worlds = try ids.flatMap { try World.find($0) }
+            worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+            worlds = try worlds.flatMap { world in
+                let modifiedWorld = world
+                try modifiedWorld.save()
+                return modifiedWorld
+            }
+            let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+            return JSON(updatedWorlds)
+        }
+    }
+}
+
+/// Since Routes doesn't depend on anything
+/// to be initialized, we can conform it to EmptyInitializable
+///
+/// This will allow it to be passed by type.
+extension Routes: EmptyInitializable { }

+ 0 - 52
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/World.swift

@@ -1,52 +0,0 @@
-import Vapor
-
-final class World: Model {
-  
-  static let entity = "world"
-  
-  var mongoId: Node?
-  var id: Node?
-  var randomNumber: Int32
-  
-  static var idKey = "_id"
-  
-  // For internal Vapor use
-  var exists: Bool = false
-  
-  init(node: Node, in context: Context) throws {
-    mongoId = try node.extract("id")
-    id = try node.extract("_id")
-    randomNumber = try node.extract("randomNumber")
-  }
-  
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": mongoId,
-      "_id": id,
-      "randomNumber": randomNumber
-    ])
-  }
-  
-  func makeJSONNode() throws -> Node {
-    return try Node(node: [
-      "id": id?.int,
-      "randomNumber": randomNumber
-    ])
-  }
-  
-  func makeJSON() throws -> JSON {
-    let node = try makeJSONNode()
-    return try JSON(node: node)
-  }
-  
-  static func prepare(_ database: Database) throws {
-    try database.create("World") { world in
-      world.id("id")
-      world.int("randomNumber")
-    }
-  }
-  
-  static func revert(_ database: Database) throws {
-    try database.delete("World")
-  }
-}

+ 25 - 79
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/main.swift

@@ -1,79 +1,25 @@
-import Foundation
-import Vapor
-import JSON
-import HTTP
-import VaporMongo
-
-import TfbCommon
-
-let drop = Droplet()
-try drop.addProvider(VaporMongo.Provider.self)
-
-// All test types require `Server` and `Date` HTTP response headers.
-// Vapor has standard middleware that adds `Date` header.
-// We use custom middleware that adds `Server` header.
-drop.middleware.append(ServerMiddleware())
-
-// Normally we would add preparation for Fluent Models.
-//   `drop.preparations.append(World.self)` etc.
-// During preparation Fluent creates `fluent` table to track migrations.
-// But TFB environment does not grant user rights to create tables.
-// So we just configure our Models with correct database.
-World.database = drop.database
-Fortune.database = drop.database
-
-// Test type 1: JSON serialization
-drop.get("json") { req in
-  return try JSON(node: Message("Hello, World!"))
-}
-
-// Test type 2: Single database query
-drop.get("db") { _ in
-  let worldId = WorldMeta.randomId()
-  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
-}
-
-// Test type 3: Multiple database queries
-drop.get("queries") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
-  return JSON(worlds)
-}
-
-// Test type 4: Fortunes
-private let posixLocale = Locale(identifier: "en_US_POSIX")
-drop.get("fortunes") { _ in
-  var fortunes = try Fortune.all()
-  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
-  fortunes.insert(additional, at: 0)
-  fortunes.sort(by: { lhs, rhs -> Bool in
-    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
-  })
-  
-  let nodes = try fortunes.map { try $0.makeJSONNode() }
-  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
-}
-
-// Test type 5: Database updates
-drop.get("updates") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  var worlds = try ids.flatMap { try World.find($0) }
-  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
-  worlds = try worlds.flatMap { world in
-    var modifiedWorld = world
-    try modifiedWorld.save()
-    return modifiedWorld
-  }
-  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
-  return JSON(updatedWorlds)
-}
-
-// Test type 6: Plaintext
-let helloWorldBuffer = "Hello, World!".utf8.array
-drop.get("plaintext") { req in
-  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
-}
-
-drop.run()
+/// We have isolated all of our App's logic into
+/// the App module because it makes our app
+/// more testable.
+///
+/// In general, the executable portion of our App
+/// shouldn't include much more code than is presented
+/// here.
+///
+/// We simply initialize our Droplet, optionally
+/// passing in values if necessary
+/// Then, we pass it to our App's setup function
+/// this should setup all the routes and special
+/// features of our app
+///
+/// .run() runs the Droplet's commands, 
+/// if no command is given, it will default to "serve"
+var config = try Config()
+
+try config.set("fluent.driver", "mongo")
+try config.setup()
+
+let drop = try Droplet(config)
+
+try drop.setup()
+try drop.run()

+ 30 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Config+Setup.swift

@@ -0,0 +1,30 @@
+import MySQLProvider
+import TfbCommon
+import LeafProvider
+
+extension Config {
+    public func setup() throws {
+        // allow fuzzy conversions for these types
+        // (add your own types here)
+        Node.fuzzy = [Row.self, JSON.self, Node.self]
+
+        try setupProviders()
+        try setupPreparations()
+        
+        self.addConfigurable(middleware: ServerMiddleware(), name: "server")
+        self.addConfigurable(middleware: ContentMiddleware(), name: "content-header")
+    }
+    
+    /// Configure providers
+    private func setupProviders() throws {
+        try addProvider(MySQLProvider.Provider.self)
+        try addProvider(LeafProvider.Provider.self)
+    }
+    
+    /// Add all models that should have their
+    /// schemas prepared before the app boots
+    private func setupPreparations() throws {
+        preparations.append(Fortune.self)
+        preparations.append(World.self)
+    }
+}

+ 10 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Droplet+Setup.swift

@@ -0,0 +1,10 @@
+@_exported import Vapor
+
+extension Droplet {
+    public func setup() throws {
+        try collection(Routes.self)
+
+        Fortune.database = try drop.assertDatabase()
+        World.database = try drop.assertDatabase()
+    }
+}

+ 0 - 42
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Fortune.swift

@@ -1,42 +0,0 @@
-import Vapor
-
-final class Fortune: Model {
-
-  static let entity = "Fortune"
-
-  var id: Node?
-
-  var message: String
-
-  // For internal Vapor use
-  var exists: Bool = false
-
-  init(id: Int, message: String) {
-    self.id = Node(id)
-    self.message = message
-  }
-
-  init(node: Node, in context: Context) throws {
-    id = try node.extract("id")
-    message = try node.extract("message")
-  }
-
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": id,
-      "message": message
-    ])
-  }
-
-  static func prepare(_ database: Database) throws {
-    try database.create("Fortune") { fortune in
-      fortune.id("id")
-      fortune.string("message")
-    }
-  }
-
-  static func revert(_ database: Database) throws {
-    try database.delete("Fortune")
-  }
-
-}

+ 78 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift

@@ -0,0 +1,78 @@
+import Vapor
+import FluentProvider
+
+final class Fortune: Model {
+
+    static let entity = "fortune"
+
+    var storage: Storage = Storage()
+    var id: Node?
+    var message: String
+
+    static var idKey = "_id"
+
+    // For internal Vapor use
+    var exists: Bool = false
+
+    init(id: Int, message: String) {
+        self.id = Node(id)
+        self.message = message
+    }
+
+    init(node: Node, in context: Context) throws {
+        id = try node.get("id")
+        message = try node.get("message")
+    }
+
+    /// Initializes the Post from the
+    /// database row
+    init(row: Row) throws {
+        id = try row.get("id")
+        message  = try row.get("message")
+    }
+
+    // Serializes the Post to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", id)
+        try row.set("message", message)
+
+        return row
+    }
+
+    func makeNode(context: Context) throws -> Node {
+        return try Node(node: [
+            "id": id,
+            "message": message
+            ])
+    }
+
+    func makeJSONNode() throws -> Node {
+        return try Node(node: [
+            "id": id?.int ?? 0,
+            "message": message
+            ])
+    }
+
+    func makeJSON() throws -> JSON {
+        let node = try makeJSONNode()
+        return JSON(node: node)
+    }
+}
+
+
+
+// MARK: Fluent Preparation
+
+extension Fortune: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Fortunes
+    static func prepare(_ database: Database) throws {
+
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}

+ 79 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift

@@ -0,0 +1,79 @@
+import Vapor
+import FluentProvider
+
+final class World: Model {
+    static let entity = "world"
+    
+    let storage = Storage()
+
+    /// The content of the post
+    var id: Node?
+    var randomNumber: Int32
+
+    /// Creates a new World
+    init(id: Node, randomNumber: Int32) {
+        self.id = id
+        self.randomNumber = randomNumber
+    }
+
+    // MARK: Fluent Serialization
+
+    /// Initializes the World from the
+    /// database row
+    init(row: Row) throws {
+        id = try row.get("id")
+        randomNumber  = try row.get("randomNumber")
+    }
+
+    // Serializes the Post to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", id)
+        try row.set("randomNumber", randomNumber)
+
+        return row
+    }
+}
+
+// MARK: Fluent Preparation
+
+extension World: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Posts
+    static func prepare(_ database: Database) throws {
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}
+
+// MARK: JSON
+
+// How the model converts from / to JSON.
+// For example when:
+//     - Creating a new Post (POST /posts)
+//     - Fetching a post (GET /posts, GET /posts/:id)
+//
+extension World: JSONConvertible {
+    convenience init(json: JSON) throws {
+        try self.init(
+            id: json.get("id"),
+            randomNumber: json.get("randomNumber")
+        )
+    }
+
+    func makeJSON() throws -> JSON {
+        var json = JSON()
+        try json.set("id", id)
+        try json.set("randomNumber", randomNumber)
+        return json
+    }
+}
+
+// MARK: HTTP
+
+// This allows Post models to be returned
+// directly in route closures
+extension World: ResponseRepresentable { }

+ 73 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Routes.swift

@@ -0,0 +1,73 @@
+import Vapor
+import TfbCommon
+import Foundation
+
+final class Routes: RouteCollection {
+    func build(_ builder: RouteBuilder) throws {
+        builder.get("json") { req in
+            return try JSON(node: Message("Hello, World!"))
+        }
+
+        builder.get("plaintext") { req in
+            return "Hello, world!"
+        }
+
+        // response to requests to /info domain
+        // with a description of the request
+        builder.get("info") { req in
+            return req.description
+        }
+
+        builder.get("description") { req in return req.description }
+
+        // Test type 2: Single database query
+        builder.get("db") { _ in
+            let worldId = WorldMeta.randomId()
+            return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+        }
+
+        // Test type 3: Multiple database queries
+        builder.get("queries") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+            return JSON(worlds)
+        }
+
+        // Test type 4: Fortunes
+        let posixLocale = Locale(identifier: "en_US_POSIX")
+        builder.get("fortunes") { _ in
+            var fortunes = try Fortune.all()
+            let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+            fortunes.insert(additional, at: 0)
+            fortunes.sort(by: { lhs, rhs -> Bool in
+                return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+            })
+
+            let nodes = try fortunes.map { try $0.makeJSONNode() }
+            return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+        }
+
+        // Test type 5: Database updates
+        builder.get("updates") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            var worlds = try ids.flatMap { try World.find($0) }
+            worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+            worlds = try worlds.flatMap { world in
+                let modifiedWorld = world
+                try modifiedWorld.save()
+                return modifiedWorld
+            }
+            let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+            return JSON(updatedWorlds)
+        }
+    }
+}
+
+
+/// Since Routes doesn't depend on anything
+/// to be initialized, we can conform it to EmptyInitializable
+///
+/// This will allow it to be passed by type.
+extension Routes: EmptyInitializable { }

+ 0 - 35
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/World.swift

@@ -1,35 +0,0 @@
-import Vapor
-
-final class World: Model {
-  
-  static let entity = "World"
-  
-  var id: Node?
-  var randomNumber: Int32
-  
-  // For internal Vapor use
-  var exists: Bool = false
-  
-  init(node: Node, in context: Context) throws {
-    id = try node.extract("id")
-    randomNumber = try node.extract("randomNumber")
-  }
-  
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": id,
-      "randomNumber": randomNumber
-    ])
-  }
-  
-  static func prepare(_ database: Database) throws {
-    try database.create("World") { world in
-      world.id("id")
-      world.int("randomNumber")
-    }
-  }
-  
-  static func revert(_ database: Database) throws {
-    try database.delete("World")
-  }
-}

+ 25 - 80
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift

@@ -1,80 +1,25 @@
-import Foundation
-import Vapor
-import JSON
-import HTTP
-import VaporMySQL
-
-import TfbCommon
-
-let drop = Droplet()
-try drop.addProvider(VaporMySQL.Provider.self)
-
-// All test types require `Server` and `Date` HTTP response headers.
-// Vapor has standard middleware that adds `Date` header.
-// We use custom middleware that adds `Server` header.
-drop.middleware.append(ServerMiddleware())
-
-// Normally we would add preparation for Fluent Models.
-//   `drop.preparations.append(World.self)` etc.
-// During preparation Fluent creates `fluent` table to track migrations.
-// But TFB environment does not grant user rights to create tables.
-// So we just configure our Models with correct database.
-World.database = drop.database
-Fortune.database = drop.database
-
-// Test type 1: JSON serialization
-drop.get("json") { req in
-  return try JSON(node: Message("Hello, World!"))
-}
-
-// Test type 2: Single database query
-drop.get("db") { _ in
-  let worldId = WorldMeta.randomId()
-  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
-}
-
-// Test type 3: Multiple database queries
-drop.get("queries") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
-  return JSON(worlds)
-}
-
-// Test type 4: Fortunes
-/// Locale for string comparison to workaround https://bugs.swift.org/browse/SR-530
-private let posixLocale = Locale(identifier: "en_US_POSIX")
-drop.get("fortunes") { _ in
-  var fortunes = try Fortune.all()
-  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
-  fortunes.insert(additional, at: 0)
-  fortunes.sort(by: { lhs, rhs -> Bool in
-    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
-  })
-  
-  let nodes = try fortunes.map { try $0.makeNode() }
-  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
-}
-
-// Test type 5: Database updates
-drop.get("updates") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  var worlds = try ids.flatMap { try World.find($0) }
-  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
-  worlds = try worlds.flatMap { world in
-    var modifiedWorld = world
-    try modifiedWorld.save()
-    return modifiedWorld
-  }
-  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
-  return JSON(updatedWorlds)
-}
-
-// Test type 6: Plaintext
-let helloWorldBuffer = "Hello, World!".utf8.array
-drop.get("plaintext") { req in
-  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
-}
-
-drop.run()
+/// We have isolated all of our App's logic into
+/// the App module because it makes our app
+/// more testable.
+///
+/// In general, the executable portion of our App
+/// shouldn't include much more code than is presented
+/// here.
+///
+/// We simply initialize our Droplet, optionally
+/// passing in values if necessary
+/// Then, we pass it to our App's setup function
+/// this should setup all the routes and special
+/// features of our app
+///
+/// .run() runs the Droplet's commands, 
+/// if no command is given, it will default to "serve"
+var config = try Config()
+
+try config.set("fluent.driver", "mysql")
+try config.setup()
+
+let drop = try Droplet(config)
+
+try drop.setup()
+try drop.run()

+ 30 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Config+Setup.swift

@@ -0,0 +1,30 @@
+import PostgreSQLProvider
+import TfbCommon
+import LeafProvider
+
+extension Config {
+    public func setup() throws {
+        // allow fuzzy conversions for these types
+        // (add your own types here)
+        Node.fuzzy = [Row.self, JSON.self, Node.self]
+
+        try setupProviders()
+        try setupPreparations()
+        
+        self.addConfigurable(middleware: ServerMiddleware(), name: "server")
+        self.addConfigurable(middleware: ContentMiddleware(), name: "content-header")
+    }
+    
+    /// Configure providers
+    private func setupProviders() throws {
+        try addProvider(PostgreSQLProvider.Provider.self)
+        try addProvider(LeafProvider.Provider.self)
+    }
+    
+    /// Add all models that should have their
+    /// schemas prepared before the app boots
+    private func setupPreparations() throws {
+        preparations.append(Fortune.self)
+        preparations.append(World.self)
+    }
+}

+ 10 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Droplet+Setup.swift

@@ -0,0 +1,10 @@
+@_exported import Vapor
+
+extension Droplet {
+    public func setup() throws {
+        try collection(Routes.self)
+
+        Fortune.database = try drop.assertDatabase()
+        World.database = try drop.assertDatabase()
+    }
+}

+ 0 - 42
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Fortune.swift

@@ -1,42 +0,0 @@
-import Vapor
-
-final class Fortune: Model {
-
-  static let entity = "Fortune"
-
-  var id: Node?
-
-  var message: String
-
-  // For internal Vapor use
-  var exists: Bool = false
-
-  init(id: Int, message: String) {
-    self.id = Node(id)
-    self.message = message
-  }
-
-  init(node: Node, in context: Context) throws {
-    id = try node.extract("id")
-    message = try node.extract("message")
-  }
-
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": id,
-      "message": message
-    ])
-  }
-
-  static func prepare(_ database: Database) throws {
-    try database.create("Fortune") { fortune in
-      fortune.id("id")
-      fortune.string("message")
-    }
-  }
-
-  static func revert(_ database: Database) throws {
-    try database.delete("Fortune")
-  }
-
-}

+ 74 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Models/Fortune.swift

@@ -0,0 +1,74 @@
+import Vapor
+import FluentProvider
+
+final class Fortune: Model {
+    static let entity = "fortune"
+
+    var storage: Storage = Storage()
+    var id: Node
+    var message: String
+
+    // For internal Vapor use
+    var exists: Bool = false
+
+    init(id: Int, message: String) {
+        self.id = Node(id)
+        self.message = message
+    }
+
+    init(node: Node, in context: Context) throws {
+        id = try node.get("id")
+        message = try node.get("message")
+    }
+
+    /// Initializes the Post from the
+    /// database row
+    init(row: Row) throws {
+        id = try row.get("id")
+        message  = try row.get("message")
+    }
+
+    // Serializes the Post to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", id)
+        try row.set("message", message)
+
+        return row
+    }
+
+    func makeNode(context: Context) throws -> Node {
+        return try Node(node: [
+            "id": id,
+            "message": message
+            ])
+    }
+
+    func makeJSONNode() throws -> Node {
+        return try Node(node: [
+            "id": id,
+            "message": message
+            ])
+    }
+
+    func makeJSON() throws -> JSON {
+        let node = try makeJSONNode()
+        return JSON(node: node)
+    }
+}
+
+
+
+// MARK: Fluent Preparation
+
+extension Fortune: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Fortunes
+    static func prepare(_ database: Database) throws {
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}

+ 74 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Models/World.swift

@@ -0,0 +1,74 @@
+import Vapor
+import FluentProvider
+
+final class World: Model {
+    static let entity = "world"
+    
+    let storage = Storage()
+
+    /// The content of the post
+    var id: Node
+    var randomNumber: Int32
+
+    /// Creates a new World
+    init(id: Node, randomNumber: Int32) {
+        self.id = id
+        self.randomNumber = randomNumber
+    }
+
+    // MARK: Fluent Serialization
+
+    /// Initializes the World from the
+    /// database row
+    init(row: Row) throws {
+        id = try row.get("id")
+        randomNumber  = try row.get("randomnumber")
+    }
+
+    // Serializes the Post to the database
+    func makeRow() throws -> Row {
+        var row = Row()
+
+        try row.set("id", id)
+        try row.set("randomnumber", randomNumber)
+
+        return row
+    }
+}
+
+// MARK: Fluent Preparation
+
+extension World: Preparation {
+    /// Prepares a table/collection in the database
+    /// for storing Posts
+    static func prepare(_ database: Database) throws {
+    }
+
+    /// Undoes what was done in `prepare`
+    static func revert(_ database: Database) throws {
+    }
+}
+
+// MARK: JSON
+
+extension World: JSONConvertible {
+    convenience init(json: JSON) throws {
+        try self.init(
+            id: json.get("id"),
+            randomNumber: json.get("randomnumber")
+        )
+    }
+
+    func makeJSON() throws -> JSON {
+        var json = JSON()
+        try json.set("id", id)
+        try json.set("randomnumber", randomNumber)
+        return json
+    }
+}
+
+// MARK: HTTP
+
+// This allows Post models to be returned
+// directly in route closures
+extension World: ResponseRepresentable { }

+ 72 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Routes.swift

@@ -0,0 +1,72 @@
+import Vapor
+import TfbCommon
+import Foundation
+
+final class Routes: RouteCollection {
+    func build(_ builder: RouteBuilder) throws {
+        builder.get("json") { req in
+            return try JSON(node: Message("Hello, World!"))
+        }
+
+        builder.get("plaintext") { req in
+            return "Hello, world!"
+        }
+
+        // response to requests to /info domain
+        // with a description of the request
+        builder.get("info") { req in
+            return req.description
+        }
+
+        builder.get("description") { req in return req.description }
+
+        // Test type 2: Single database query
+        builder.get("db") { _ in
+            let worldId = WorldMeta.randomId()
+            return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+        }
+
+        // Test type 3: Multiple database queries
+        builder.get("queries") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+            return JSON(worlds)
+        }
+
+        // Test type 4: Fortunes
+        let posixLocale = Locale(identifier: "en_US_POSIX")
+        builder.get("fortunes") { _ in
+            var fortunes = try Fortune.all()
+            let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+            fortunes.insert(additional, at: 0)
+            fortunes.sort(by: { lhs, rhs -> Bool in
+                return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+            })
+
+            let nodes = try fortunes.map { try $0.makeJSONNode() }
+            return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+        }
+
+        // Test type 5: Database updates
+        builder.get("updates") { req in
+            let queries = queriesParam(for: req)
+            let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+            var worlds = try ids.flatMap { try World.find($0) }
+            worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+            worlds = try worlds.flatMap { world in
+                let modifiedWorld = world
+                try modifiedWorld.save()
+                return modifiedWorld
+            }
+            let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+            return JSON(updatedWorlds)
+        }
+    }
+}
+
+/// Since Routes doesn't depend on anything
+/// to be initialized, we can conform it to EmptyInitializable
+///
+/// This will allow it to be passed by type.
+extension Routes: EmptyInitializable { }

+ 0 - 35
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/World.swift

@@ -1,35 +0,0 @@
-import Vapor
-
-final class World: Model {
-  
-  static let entity = "World"
-  
-  var id: Node?
-  var randomNumber: Int32
-  
-  // For internal Vapor use
-  var exists: Bool = false
-  
-  init(node: Node, in context: Context) throws {
-    id = try node.extract("id")
-    randomNumber = try node.extract("randomnumber")
-  }
-  
-  func makeNode(context: Context) throws -> Node {
-    return try Node(node: [
-      "id": id,
-      "randomNumber": randomNumber
-    ])
-  }
-  
-  static func prepare(_ database: Database) throws {
-    try database.create("World") { world in
-      world.id("id")
-      world.int("randomNumber")
-    }
-  }
-  
-  static func revert(_ database: Database) throws {
-    try database.delete("World")
-  }
-}

+ 25 - 79
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/main.swift

@@ -1,79 +1,25 @@
-import Foundation
-import Vapor
-import JSON
-import HTTP
-import VaporPostgreSQL
-
-import TfbCommon
-
-let drop = Droplet()
-try drop.addProvider(VaporPostgreSQL.Provider.self)
-
-// All test types require `Server` and `Date` HTTP response headers.
-// Vapor has standard middleware that adds `Date` header.
-// We use custom middleware that adds `Server` header.
-drop.middleware.append(ServerMiddleware())
-
-// Normally we would add preparation for Fluent Models.
-//   `drop.preparations.append(World.self)` etc.
-// During preparation Fluent creates `fluent` table to track migrations.
-// But TFB environment does not grant user rights to create tables.
-// So we just configure our Models with correct database.
-World.database = drop.database
-Fortune.database = drop.database
-
-// Test type 1: JSON serialization
-drop.get("json") { req in
-  return try JSON(node: Message("Hello, World!"))
-}
-
-// Test type 2: Single database query
-drop.get("db") { _ in
-  let worldId = WorldMeta.randomId()
-  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
-}
-
-// Test type 3: Multiple database queries
-drop.get("queries") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
-  return JSON(worlds)
-}
-
-// Test type 4: Fortunes
-private let posixLocale = Locale(identifier: "en_US_POSIX")
-drop.get("fortunes") { _ in
-  var fortunes = try Fortune.all()
-  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
-  fortunes.insert(additional, at: 0)
-  fortunes.sort(by: { lhs, rhs -> Bool in
-    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
-  })
-  
-  let nodes = try fortunes.map { try $0.makeNode() }
-  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
-}
-
-// Test type 5: Database updates
-drop.get("updates") { req in
-  let queries = queriesParam(for: req)
-  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
-  var worlds = try ids.flatMap { try World.find($0) }
-  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
-  worlds = try worlds.flatMap { world in
-    var modifiedWorld = world
-    try modifiedWorld.save()
-    return modifiedWorld
-  }
-  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
-  return JSON(updatedWorlds)
-}
-
-// Test type 6: Plaintext
-let helloWorldBuffer = "Hello, World!".utf8.array
-drop.get("plaintext") { req in
-  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
-}
-
-drop.run()
+/// We have isolated all of our App's logic into
+/// the App module because it makes our app
+/// more testable.
+///
+/// In general, the executable portion of our App
+/// shouldn't include much more code than is presented
+/// here.
+///
+/// We simply initialize our Droplet, optionally
+/// passing in values if necessary
+/// Then, we pass it to our App's setup function
+/// this should setup all the routes and special
+/// features of our app
+///
+/// .run() runs the Droplet's commands, 
+/// if no command is given, it will default to "serve"
+var config = try Config()
+
+try config.set("fluent.driver", "postgresql")
+try config.setup()
+
+let drop = try Droplet(config)
+
+try drop.setup()
+try drop.run()

+ 1 - 1
frameworks/Swift/vapor/setup-mongodb.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-fw_depends swift3 mongodb mysql postgresql
+fw_depends swift3 ctls cmysql mongodb mysql postgresql
 
 swift build -Xswiftc -DNOJSON -c release
 

+ 1 - 1
frameworks/Swift/vapor/setup-mysql.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-fw_depends swift3 mongodb mysql postgresql
+fw_depends swift3 ctls cmysql mongodb mysql postgresql
 
 swift build -Xswiftc -DNOJSON -c release
 

+ 1 - 1
frameworks/Swift/vapor/setup-postgresql.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-fw_depends swift3 mongodb mysql postgresql
+fw_depends swift3 ctls cmysql mongodb mysql postgresql
 
 swift build -Xswiftc -DNOJSON -c release
 

+ 2 - 2
toolset/setup/linux/languages/swift3.sh

@@ -11,8 +11,8 @@ sudo apt-add-repository --yes ppa:george-edison55/cmake-3.x
 sudo apt-get update -qq
 sudo apt-get install -qqy cmake
 
-SWIFT_SNAPSHOT="swift-3.0.2-RELEASE"
-SWIFT_SNAPSHOT_LOWERCASE="swift-3.0.2-release"
+SWIFT_SNAPSHOT="swift-3.1.1-RELEASE"
+SWIFT_SNAPSHOT_LOWERCASE="swift-3.1.1-release"
 UBUNTU_VERSION="ubuntu14.04"
 UBUNTU_VERSION_NO_DOTS="ubuntu1404"
 

+ 14 - 0
toolset/setup/linux/systools/cmysql.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+fw_depends swift3
+
+fw_installed cmysql && return 0
+
+echo "Installing cmysql"
+
+eval "$(curl -sL https://apt.vapor.sh)"
+sudo apt-get install cmysql
+
+echo -e "export PATH=~/.local/bin:\$PATH" > $IROOT/cmysql.installed
+
+source $IROOT/cmysql.installed

+ 14 - 0
toolset/setup/linux/systools/ctls.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+fw_depends swift3
+
+fw_installed ctls && return 0
+
+echo "Installing ctls"
+
+eval "$(curl -sL https://apt.vapor.sh)"
+sudo apt-get install ctls
+
+echo -e "export PATH=~/.local/bin:\$PATH" > $IROOT/ctls.installed
+
+source $IROOT/ctls.installed