├── .gitignore ├── .swift-version ├── .travis.yml ├── Dockerfile ├── Integration Tests ├── SwiftServer%20localhost.postman_environment.json └── SwiftServer.postman_collection.json ├── LICENSE ├── Package.pins ├── Package.swift ├── README.md ├── Sources ├── SwiftServer │ └── main.swift └── SwiftServerLibrary │ ├── CRUDMongoEndpoint.swift │ ├── CRUDMongoHandler.swift │ ├── CRUDMongoProvider.swift │ └── ItemEndpoint.swift ├── Tests ├── LinuxMain.swift └── SwiftServerLibraryTests │ └── ItemTests.swift ├── docker-compose-ci.yml ├── docker-compose-common.yml ├── docker-compose-dev.yml ├── docker-compose-prod.yml ├── run-integration-tests.sh └── swift+docker.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xcuserstate 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | Packages/ 40 | .build/ 41 | *.xcodeproj 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "4" 5 | 6 | services: 7 | - docker 8 | 9 | install: 10 | - docker --version 11 | - docker-compose --version 12 | - docker-compose -f docker-compose-ci.yml build 13 | - docker-compose -f docker-compose-ci.yml up -d 14 | 15 | before_script: 16 | - npm install -g newman 17 | 18 | script: 19 | - ./run-integration-tests.sh 20 | 21 | after_success: 22 | - if [ "$TRAVIS_BRANCH" == "master" ]; then 23 | docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; 24 | docker tag choefele/swift-server-app choefele/swift-server-app:${TRAVIS_COMMIT::8}-$TRAVIS_BUILD_NUMBER; 25 | docker push choefele/swift-server-app; 26 | fi -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:3.1 2 | MAINTAINER Claus 3 | 4 | WORKDIR /app 5 | 6 | RUN apt-get update && apt-get install -y \ 7 | libssl-dev \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY Package.swift ./ 11 | RUN swift package fetch; exit 0 12 | 13 | EXPOSE 8090 14 | 15 | COPY Sources ./Sources 16 | COPY Tests ./Tests 17 | RUN swift test 18 | CMD ./.build/debug/SwiftServer -------------------------------------------------------------------------------- /Integration Tests/SwiftServer%20localhost.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "7cc567d4-abb7-4958-d4ec-a72c3844f59a", 3 | "name": "SwiftServer localhost", 4 | "values": [ 5 | { 6 | "key": "base_url", 7 | "type": "text", 8 | "value": "localhost:8090", 9 | "enabled": true 10 | } 11 | ], 12 | "timestamp": 1474226039665, 13 | "_postman_variable_scope": "environment", 14 | "_postman_exported_at": "2016-09-18T19:15:57.809Z", 15 | "_postman_exported_using": "Postman/4.7.1" 16 | } -------------------------------------------------------------------------------- /Integration Tests/SwiftServer.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f807269b-a362-48e1-9c20-93a283b090f1", 3 | "name": "SwiftServer", 4 | "description": "", 5 | "order": [ 6 | "53702c95-6b93-dcee-e940-ffb951a90513", 7 | "f9556291-8c43-39ae-8792-801b9518c068", 8 | "baa6cff1-9b70-3ee2-c685-c50cea818e8c" 9 | ], 10 | "folders": [], 11 | "timestamp": 1474219392030, 12 | "owner": "", 13 | "public": false, 14 | "published": false, 15 | "requests": [ 16 | { 17 | "id": "53702c95-6b93-dcee-e940-ffb951a90513", 18 | "headers": "", 19 | "url": "{{base_url}}/ping", 20 | "preRequestScript": null, 21 | "pathVariables": {}, 22 | "method": "GET", 23 | "data": null, 24 | "dataMode": "params", 25 | "version": 2, 26 | "tests": "tests[\"Status code is 200\"] = responseCode.code === 200;\ntests[\"Body is correct\"] = responseBody === \"pong\";", 27 | "currentHelper": "normal", 28 | "helperAttributes": {}, 29 | "time": 1474220487911, 30 | "name": "Ping", 31 | "description": "", 32 | "collectionId": "f807269b-a362-48e1-9c20-93a283b090f1", 33 | "responses": [] 34 | }, 35 | { 36 | "id": "baa6cff1-9b70-3ee2-c685-c50cea818e8c", 37 | "headers": "", 38 | "url": "{{base_url}}/items", 39 | "preRequestScript": null, 40 | "pathVariables": {}, 41 | "method": "POST", 42 | "data": null, 43 | "dataMode": "params", 44 | "version": 2, 45 | "tests": "tests[\"Status code is 201\"] = responseCode.code === 201;\n\nvar jsonData = JSON.parse(responseBody);\ntests[\"Created item has ID\"] = jsonData.id !== null;", 46 | "currentHelper": "normal", 47 | "helperAttributes": {}, 48 | "time": 1474223954276, 49 | "name": "Create item", 50 | "description": "", 51 | "collectionId": "f807269b-a362-48e1-9c20-93a283b090f1", 52 | "responses": [] 53 | }, 54 | { 55 | "id": "f9556291-8c43-39ae-8792-801b9518c068", 56 | "headers": "", 57 | "url": "{{base_url}}/items", 58 | "preRequestScript": null, 59 | "pathVariables": {}, 60 | "method": "GET", 61 | "data": null, 62 | "dataMode": "params", 63 | "tests": "tests[\"Status code is 200\"] = responseCode.code === 200;\ntests[\"Response time is less than 200ms\"] = responseTime < 100;\n\nvar jsonData = JSON.parse(responseBody);\nvar schema = {\n \"type\": \"object\",\n \"required\": [ \"items\" ],\n \"properties\": {\n \"items\": { \n \"type\": \"array\" ,\n \"items\": {\n \"type\": \"object\",\n \"required\": [ \"id\" ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"minLength\": 24\n }\n }\n }\n }\n }\n};\ntests[\"Valid JSON data\"] = tv4.validate(jsonData, schema);\nif (tv4.error) {\n console.log(\"Validation failed: \", tv4.error);\n}", 64 | "currentHelper": "normal", 65 | "helperAttributes": {}, 66 | "time": 1474226034849, 67 | "name": "Read items", 68 | "description": "", 69 | "collectionId": "f807269b-a362-48e1-9c20-93a283b090f1", 70 | "responses": [] 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Claus Höfele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.pins: -------------------------------------------------------------------------------- 1 | { 2 | "autoPin": true, 3 | "pins": [ 4 | { 5 | "package": "BSON", 6 | "reason": null, 7 | "repositoryURL": "https://github.com/OpenKitten/BSON.git", 8 | "version": "5.1.4" 9 | }, 10 | { 11 | "package": "CCurl", 12 | "reason": null, 13 | "repositoryURL": "https://github.com/IBM-Swift/CCurl.git", 14 | "version": "0.4.0" 15 | }, 16 | { 17 | "package": "Cheetah", 18 | "reason": null, 19 | "repositoryURL": "https://github.com/OpenKitten/Cheetah.git", 20 | "version": "1.0.3" 21 | }, 22 | { 23 | "package": "CryptoKitten", 24 | "reason": null, 25 | "repositoryURL": "https://github.com/OpenKitten/CryptoKitten.git", 26 | "version": "0.2.1" 27 | }, 28 | { 29 | "package": "Environment", 30 | "reason": null, 31 | "repositoryURL": "https://github.com/Danappelxx/Environment.git", 32 | "version": "0.6.0" 33 | }, 34 | { 35 | "package": "HeliumLogger", 36 | "reason": null, 37 | "repositoryURL": "https://github.com/IBM-Swift/HeliumLogger.git", 38 | "version": "1.7.0" 39 | }, 40 | { 41 | "package": "KittenCore", 42 | "reason": null, 43 | "repositoryURL": "https://github.com/OpenKitten/KittenCore.git", 44 | "version": "0.2.3" 45 | }, 46 | { 47 | "package": "Kitura", 48 | "reason": null, 49 | "repositoryURL": "https://github.com/IBM-Swift/Kitura.git", 50 | "version": "1.7.7" 51 | }, 52 | { 53 | "package": "Kitura-TemplateEngine", 54 | "reason": null, 55 | "repositoryURL": "https://github.com/IBM-Swift/Kitura-TemplateEngine.git", 56 | "version": "1.7.1" 57 | }, 58 | { 59 | "package": "Kitura-net", 60 | "reason": null, 61 | "repositoryURL": "https://github.com/IBM-Swift/Kitura-net.git", 62 | "version": "1.7.15" 63 | }, 64 | { 65 | "package": "LoggerAPI", 66 | "reason": null, 67 | "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git", 68 | "version": "1.7.0" 69 | }, 70 | { 71 | "package": "MongoKitten", 72 | "reason": null, 73 | "repositoryURL": "https://github.com/PlanTeam/MongoKitten.git", 74 | "version": "4.0.14" 75 | }, 76 | { 77 | "package": "SSLService", 78 | "reason": null, 79 | "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git", 80 | "version": "0.12.48" 81 | }, 82 | { 83 | "package": "Schrodinger", 84 | "reason": null, 85 | "repositoryURL": "https://github.com/OpenKitten/Schrodinger.git", 86 | "version": "1.0.1" 87 | }, 88 | { 89 | "package": "Socket", 90 | "reason": null, 91 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git", 92 | "version": "0.12.61" 93 | }, 94 | { 95 | "package": "SwiftyJSON", 96 | "reason": null, 97 | "repositoryURL": "https://github.com/IBM-Swift/SwiftyJSON.git", 98 | "version": "16.0.1" 99 | } 100 | ], 101 | "version": 1 102 | } -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "SwiftServer", 5 | targets: [ 6 | Target(name: "SwiftServerLibrary"), 7 | Target(name: "SwiftServer", dependencies: ["SwiftServerLibrary"]) 8 | ], 9 | dependencies: [ 10 | .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 7), 11 | .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1, minor: 7), 12 | .Package(url: "https://github.com/PlanTeam/MongoKitten.git", majorVersion: 4, minor: 0), 13 | .Package(url: "https://github.com/Danappelxx/Environment.git", majorVersion: 0, minor: 6) 14 | ]) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Swift + Docker 3 |

4 | 5 | # Swift Server App 6 | Template to build a server app with Swift and Docker 7 | 8 | [![Build Status](https://travis-ci.org/choefele/swift-server-app.svg?branch=master)](https://travis-ci.org/choefele/swift-server-app) 9 | 10 | ## Tools 11 | - Xcode 12 | - Download from [Xcode 8](https://developer.apple.com/download/) 13 | - Select Xcode 8 as default `sudo xcode-select -s /Applications/Xcode-beta.app/Contents/Developer/` 14 | - `swiftenv` (optional since Swift built into Xcode 8 is the currently the latest version) 15 | - Install `swiftenv` via [Homebrew](https://swiftenv.fuller.li/en/latest/installation.html#via-homebrew) 16 | - `swiftenv rehash`, `swiftenv install ` (see `.swift-version`) 17 | - Docker 18 | - Install from [Docker website](https://www.docker.com/products/overview) 19 | - Consider installing [Kitematic](https://www.docker.com/products/docker-kitematic) to simplify Docker management 20 | 21 | ## Build & Run with Swift Package Manager 22 | - Run `swift build` in root folder, wait until dependencies have been downloaded and server has been built 23 | - Run dependent services `docker-compose -f docker-compose-dev.yml up` 24 | - Run server `./.build/debug/SwiftServer` 25 | - Test server by executing `curl http://localhost:8090/ping` 26 | - Test DB with `curl -X POST localhost:8090/items`, `curl http://localhost:8090/items` 27 | - Run unit tests with `swift test` 28 | 29 | ## Build & Run with Xcode 30 | - Run `swift package fetch` in root folder to update dependencies 31 | - Generate Xcode project with `swift package generate-xcodeproj` 32 | - Run dependent services `docker-compose -f docker-compose-dev.yml up` 33 | - Open `SwiftServer.xcodeproj` in Xcode and Run `SwiftServer` scheme 34 | - Test server by executing `curl http://localhost:8090/ping` 35 | - Test DB with `curl -X POST localhost:8090/items`, `curl http://localhost:8090/items` 36 | - Run unit tests with CMD-U 37 | 38 | ## Build & Run in Docker 39 | - Build image with `docker-compose -f docker-compose-ci.yml build`; tests a run as part of the build process 40 | - Run with `docker-compose -f docker-compose-ci.yml up [-d]` (stop: `docker-compose down [-v]`, logs: `docker-compose logs -f`) 41 | - Test server by executing `curl http://localhost:8090/ping` 42 | - Test DB with `curl -X POST localhost:8090/items`, `curl http://localhost:8090/items` 43 | 44 | ### Connect `mongo` to database server 45 | - `docker-compose run --rm db mongo mongodb://db` to connect to database 46 | -- `use test`, `db.items.insert({})`, `db.items.find()` to create sample data 47 | - Restart db instance to see that data persists in volume container 48 | 49 | ### Handle managed volumes 50 | - `docker inspect -f "{{json .Mounts}}" swiftserver_db_1` to find out mount point 51 | - `docker volume ls -f dangling=true` to find orphaned managed volumes 52 | - `docker volume rm $(docker volume ls -qf dangling=true)` to remove orphaned volumes 53 | 54 | ### Provision on Digital Ocean 55 | - `docker-machine create --driver digitalocean --digitalocean-access-token SwiftServer` 56 | - `eval "$(docker-machine env SwiftServer)"`, `eval "$(docker-machine env -u)"` 57 | - `docker-machine ssh SwiftServer` to ssh into new machine 58 | - Export/import ssh setup: `https://github.com/bhurlow/machine-share` 59 | - `docker compose -f docker-compose-prod.yml up` to start services 60 | 61 | ## Integration tests 62 | - Install `newman` with `npm install newman --global` 63 | - Run `./run-integration-tests.sh` 64 | -------------------------------------------------------------------------------- /Sources/SwiftServer/main.swift: -------------------------------------------------------------------------------- 1 | import Kitura 2 | import HeliumLogger 3 | import LoggerAPI 4 | import MongoKitten 5 | import Environment 6 | import SwiftServerLibrary 7 | 8 | HeliumLogger.use() 9 | 10 | for e in Env.all() { 11 | Log.info("\(e.key) = \(e.value)") 12 | } 13 | 14 | let dbUrl = Env["DB_URL"] ?? "mongodb://localhost" 15 | do { 16 | let mongoServer = try Server(dbUrl) 17 | Log.info("Connected to Mongo DB \(dbUrl)") 18 | 19 | let router = Router() 20 | 21 | let itemEndpoint = CRUDMongoEndpoint(collection: mongoServer["test"]["items"], 22 | generateDocument: ItemEndpoint.generateDocument, 23 | generateJsonDictionary: ItemEndpoint.generateJsonDictionary) 24 | let itemHandler = CRUDMongoHandler(endpoint: itemEndpoint) 25 | router.all("/items", handler: itemHandler.handleItems) 26 | router.all("/items/:id", handler: itemHandler.handleItem) 27 | 28 | router.get("/ping") { request, response, next in 29 | response.send("pong") 30 | next() 31 | } 32 | 33 | Kitura.addHTTPServer(onPort: 8090, with: router) 34 | Kitura.run() 35 | } catch { 36 | Log.error("Cannot connect to Mongo DB \(dbUrl)") 37 | } 38 | -------------------------------------------------------------------------------- /Sources/SwiftServerLibrary/CRUDMongoEndpoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRUDEndpoint.swift 3 | // SwiftServer 4 | // 5 | // Created by Claus Höfele on 25.08.16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import MongoKitten 11 | 12 | #if os(Linux) 13 | public typealias AnyType = Any 14 | #else 15 | public typealias AnyType = AnyObject 16 | #endif 17 | 18 | public struct CRUDMongoEndpoint { 19 | public init(collection: MongoKitten.Collection, generateDocument: @escaping ([String: String]) -> Document, generateJsonDictionary: @escaping (Document) -> [String: Any]) { 20 | self.collection = collection 21 | self.generateDocument = generateDocument 22 | self.generateJsonDictionary = generateJsonDictionary 23 | } 24 | 25 | public let collection: MongoKitten.Collection 26 | public var generateDocument: ([String: String]) -> Document 27 | public var generateJsonDictionary: (Document) -> [String: Any] 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SwiftServerLibrary/CRUDMongoHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRUDHelper.swift 3 | // SwiftServer 4 | // 5 | // Created by Claus Höfele on 21.08.16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Kitura 11 | import SwiftyJSON 12 | import MongoKitten 13 | 14 | // http://www.restapitutorial.com/lessons/httpmethods.html 15 | 16 | public class CRUDMongoHandler { 17 | let mongoProvider: CRUDMongoProvider 18 | let endpoint: CRUDMongoEndpoint 19 | 20 | public init(endpoint: CRUDMongoEndpoint) { 21 | self.mongoProvider = CRUDMongoProvider(collection: endpoint.collection) 22 | self.endpoint = endpoint 23 | } 24 | 25 | public func handleItems(request: RouterRequest, response: RouterResponse, next: () -> Void) throws { 26 | defer { 27 | next() 28 | } 29 | 30 | if request.method == .get { 31 | let documents = try mongoProvider.readItems(query: Query()) 32 | let jsonDictionaries = documents.map(endpoint.generateJsonDictionary) 33 | response.send(json: JSON(["items": jsonDictionaries])) 34 | } else if request.method == .post { 35 | guard let document = try mongoProvider.createItem(document: endpoint.generateDocument(request.parameters)) else { 36 | try response.send(status: .internalServerError).end() 37 | return 38 | } 39 | 40 | let jsonDictionary = endpoint.generateJsonDictionary(document) 41 | response.status(.created).send(json: JSON(jsonDictionary)) 42 | } else { 43 | try response.send(status: .notImplemented).end() 44 | } 45 | } 46 | 47 | public func handleItem(request: RouterRequest, response: RouterResponse, next: () -> Void) throws { 48 | defer { 49 | next() 50 | } 51 | 52 | if request.method == .get { 53 | guard let id = request.parameters["id"], 54 | let objectId = try? ObjectId(id), 55 | let document = try mongoProvider.readItem(objectId: objectId) else { 56 | try response.send(status: .notFound).end() 57 | return 58 | } 59 | 60 | let jsonDictionary = endpoint.generateJsonDictionary(document) 61 | response.send(json: JSON(jsonDictionary)) 62 | } else { 63 | try response.send(status: .notImplemented).end() 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/SwiftServerLibrary/CRUDMongoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRUDMongoDatabaseProvider.swift 3 | // SwiftServer 4 | // 5 | // Created by Claus Höfele on 22/08/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import MongoKitten 11 | 12 | public class CRUDMongoProvider { 13 | let collection: MongoKitten.Collection 14 | 15 | init(collection: MongoKitten.Collection) { 16 | self.collection = collection 17 | } 18 | 19 | func createItem(document: Document) throws -> Document? { 20 | let objectId = try collection.insert(document) 21 | return try collection.findOne("_id" == objectId) 22 | } 23 | 24 | func readItems(query: Query) throws -> [Document] { 25 | let documents = try collection.find(query) 26 | return Array(documents) 27 | } 28 | 29 | func readItem(objectId: ObjectId) throws -> Document? { 30 | let document = try collection.findOne("_id" == objectId) 31 | return document 32 | } 33 | 34 | func deleteItem(objectId: ObjectId) throws -> Bool { 35 | let numDocuments = try collection.remove("_id" == objectId, limitedTo: 1) 36 | return numDocuments == 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/SwiftServerLibrary/ItemEndpoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Item.swift 3 | // SwiftServer 4 | // 5 | // Created by Claus Höfele on 21.08.16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import MongoKitten 11 | 12 | public struct ItemEndpoint { 13 | public static func generateDocument(parameters: [String: String]) -> Document { 14 | return Document() 15 | } 16 | 17 | public static func generateJsonDictionary(document: Document) -> [String: Any] { 18 | var dictionary = [String: Any]() 19 | 20 | dictionary["id"] = ObjectId(document["_id"])?.hexString 21 | dictionary["name"] = String(document["name"]) 22 | 23 | return dictionary 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import XCTest 3 | 4 | @testable import SwiftServerLibraryTests 5 | 6 | XCTMain([ 7 | testCase(ItemTests.allTests) 8 | ]) 9 | #endif -------------------------------------------------------------------------------- /Tests/SwiftServerLibraryTests/ItemTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftServerLibrary 2 | import Kitura 3 | import MongoKitten 4 | import XCTest 5 | 6 | class ItemTests: XCTestCase { 7 | static let allTests = [ 8 | ("testGetRequestStatusCode", testGetRequestStatusCode), 9 | ("testGenerateJsonDictionary", testGenerateJsonDictionary) 10 | ] 11 | 12 | func testGetRequestStatusCode() { 13 | let e = expectation(description: "test") 14 | e.fulfill() 15 | waitForExpectations(timeout: 1) 16 | 17 | XCTAssertEqual(40, 40) 18 | } 19 | 20 | func testGenerateJsonDictionary() { 21 | let id = ObjectId() 22 | let name = "name" 23 | var document = Document() 24 | document["_id"] = ObjectId(id) 25 | document["name"] = name 26 | 27 | let jsonDictionary = ItemEndpoint.generateJsonDictionary(document: document) 28 | XCTAssertEqual(jsonDictionary["id"] as? String, id.hexString) 29 | XCTAssertEqual(jsonDictionary["name"] as? String, name) 30 | } 31 | 32 | // func testRouter() { 33 | // guard let mongoServer = try? Server("mongodb://localhost", automatically: false) else { XCTAssert(false); return } 34 | // let router = Router() 35 | // 36 | // let itemEndpoint = CRUDMongoEndpoint(collection: mongoServer["db"]["items"], 37 | // generateDocument: ItemEndpoint.generateDocument, 38 | // generateJsonDictionary: ItemEndpoint.generateJsonDictionary) 39 | // let itemHandler = CRUDMongoHandler(endpoint: itemEndpoint) 40 | // router.all("/items", handler: itemHandler.handleItems) 41 | // router.all("/items/:id", handler: itemHandler.handleItem) 42 | // 43 | // 44 | // let e = expectation(description: "test") 45 | // router.get("/items") { request, response, next in 46 | // response.send("pong") 47 | // next() 48 | // } 49 | // } 50 | 51 | // func testKitura() { 52 | // // Set up router for this test 53 | // let router = Router() 54 | // 55 | // router.get("/zxcv/:p1") { request, _, next in 56 | // let parameter = request.parameters["p1"] 57 | // XCTAssertNotNil(parameter, "URL parameter p1 was nil") 58 | // next() 59 | // } 60 | // router.get("/zxcv/ploni") { request, _, next in 61 | // let parameter = request.parameters["p1"] 62 | // XCTAssertNil(parameter, "URL parameter p1 was not nil, it's value was \(parameter!)") 63 | // next() 64 | // } 65 | // router.all() { _, response, next in 66 | // response.status(.OK).send("OK") 67 | // next() 68 | // } 69 | // 70 | // performServerTest(router) { expectation in 71 | // self.performRequest("get", path: "/zxcv/ploni", callback: { response in 72 | // XCTAssertNotNil(response, "ERROR!!! ClientRequest response object was nil") 73 | // expectation.fulfill() 74 | // }) 75 | // } 76 | // } 77 | } 78 | -------------------------------------------------------------------------------- /docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | extends: 5 | file: docker-compose-common.yml 6 | service: db 7 | volumes_from: 8 | - data 9 | data: 10 | extends: 11 | file: docker-compose-common.yml 12 | service: data 13 | app: 14 | extends: 15 | file: docker-compose-common.yml 16 | service: app 17 | build: . 18 | environment: 19 | DB_URL: mongodb://db 20 | links: 21 | - db 22 | volumes: 23 | datavolume: -------------------------------------------------------------------------------- /docker-compose-common.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: mongo:3.2.9 5 | data: 6 | image: alpine:3.4 7 | volumes: 8 | - datavolume:/data/db 9 | command: echo Data container 10 | app: 11 | image: choefele/swift-server-app 12 | ports: 13 | - "8090:8090" -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | ports: 5 | - "27017:27017" 6 | extends: 7 | file: docker-compose-common.yml 8 | service: db 9 | volumes_from: 10 | - data 11 | data: 12 | extends: 13 | file: docker-compose-common.yml 14 | service: data 15 | volumes: 16 | datavolume: -------------------------------------------------------------------------------- /docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | extends: 5 | file: docker-compose-common.yml 6 | service: db 7 | volumes_from: 8 | - data 9 | data: 10 | extends: 11 | file: docker-compose-common.yml 12 | service: data 13 | app: 14 | extends: 15 | file: docker-compose-common.yml 16 | service: app 17 | environment: 18 | DB_URL: mongodb://db 19 | links: 20 | - db 21 | volumes: 22 | datavolume: -------------------------------------------------------------------------------- /run-integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | newman run -e Integration\ Tests/SwiftServer%20localhost.postman_environment.json Integration\ Tests/SwiftServer.postman_collection.json -------------------------------------------------------------------------------- /swift+docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choefele/swift-server-app/c776e6d60f06d7974507e2da941b1144cfb0ca82/swift+docker.png --------------------------------------------------------------------------------