├── Chapter 15 ├── orders │ ├── Public │ │ └── .gitkeep │ ├── Tests │ │ ├── .gitkeep │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── .gitkeep │ │ │ │ └── Todo.swift │ │ │ ├── Controllers │ │ │ │ ├── .gitkeep │ │ │ │ └── TodoController.swift │ │ │ ├── routes.swift │ │ │ ├── Migrations │ │ │ │ └── CreateTodo.swift │ │ │ └── configure.swift │ │ └── Run │ │ │ └── main.swift │ ├── Dockerfile │ ├── Package.swift │ └── README.md ├── users │ ├── Public │ │ └── .gitkeep │ ├── Tests │ │ ├── .gitkeep │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── .gitkeep │ │ │ │ └── Todo.swift │ │ │ ├── Controllers │ │ │ │ ├── .gitkeep │ │ │ │ └── TodoController.swift │ │ │ ├── routes.swift │ │ │ ├── Migrations │ │ │ │ └── CreateTodo.swift │ │ │ └── configure.swift │ │ └── Run │ │ │ └── main.swift │ ├── Dockerfile │ ├── Package.swift │ ├── README.md │ └── Package.resolved └── products │ ├── Public │ └── .gitkeep │ ├── Tests │ ├── .gitkeep │ └── AppTests │ │ └── AppTests.swift │ ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── .gitkeep │ │ │ └── Todo.swift │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ └── TodoController.swift │ │ ├── routes.swift │ │ ├── Migrations │ │ │ └── CreateTodo.swift │ │ └── configure.swift │ └── Run │ │ └── main.swift │ ├── Dockerfile │ ├── Package.swift │ └── README.md ├── Chapter 3 ├── FirstApp │ ├── Tests │ │ ├── .gitkeep │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Public │ │ └── .gitkeep │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── .gitkeep │ │ │ │ └── Todo.swift │ │ │ ├── Controllers │ │ │ │ ├── .gitkeep │ │ │ │ └── TodoController.swift │ │ │ ├── Migrations │ │ │ │ └── CreateTodo.swift │ │ │ ├── routes.swift │ │ │ └── configure.swift │ │ └── Run │ │ │ └── main.swift │ ├── .github │ │ ├── FUNDING.yml │ │ └── workflows │ │ │ └── test.yml │ ├── .dockerignore │ ├── .env │ ├── cloud.yml │ ├── .gitignore │ ├── docker-compose.yml │ ├── web.Dockerfile │ ├── Package.swift │ └── README.md ├── FirstAppExtended │ ├── Public │ │ └── .gitkeep │ ├── Tests │ │ ├── .gitkeep │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── .gitkeep │ │ │ │ ├── Todo.swift │ │ │ │ └── Example.swift │ │ │ ├── Controllers │ │ │ │ ├── .gitkeep │ │ │ │ ├── NewController.swift │ │ │ │ └── TodoController.swift │ │ │ ├── Migrations │ │ │ │ ├── CreateTodo.swift │ │ │ │ └── CreateExample.swift │ │ │ ├── configure.swift │ │ │ └── routes.swift │ │ └── Run │ │ │ └── main.swift │ ├── .github │ │ ├── FUNDING.yml │ │ └── workflows │ │ │ └── test.yml │ ├── .dockerignore │ ├── .env │ ├── cloud.yml │ ├── .gitignore │ ├── docker-compose.yml │ ├── web.Dockerfile │ ├── Package.swift │ └── README.md ├── Standard Template │ ├── Public │ │ └── .gitkeep │ ├── Tests │ │ ├── .gitkeep │ │ ├── LinuxMain.swift │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── .gitkeep │ │ │ │ └── Todo.swift │ │ │ ├── Controllers │ │ │ │ ├── .gitkeep │ │ │ │ └── TodoController.swift │ │ │ ├── boot.swift │ │ │ ├── app.swift │ │ │ ├── routes.swift │ │ │ └── configure.swift │ │ └── Run │ │ │ └── main.swift │ ├── .dockerignore │ ├── cloud.yml │ ├── .gitignore │ ├── CONTRIBUTING.md │ ├── Package.swift │ ├── .circleci │ │ └── config.yml │ ├── README.md │ └── web.Dockerfile └── HelloWorld │ ├── .gitignore │ ├── main.swift │ └── Package.swift ├── Chapter 11 └── OrderService │ ├── Tests │ ├── .gitkeep │ ├── LinuxMain.swift │ └── AppTests │ │ ├── AppTests.swift │ │ └── XCTestManifests.swift │ ├── Public │ └── .gitkeep │ ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── .gitkeep │ │ │ ├── Input │ │ │ │ ├── Product.swift │ │ │ │ ├── PaymentInput.swift │ │ │ │ ├── OrderItemInput.swift │ │ │ │ └── OrderInput.swift │ │ │ ├── Output │ │ │ │ ├── AddedPaymentResponse.swift │ │ │ │ ├── OrderItemResponse.swift │ │ │ │ └── OrderResponse.swift │ │ │ ├── Payload.swift │ │ │ └── Database │ │ │ │ ├── OrderPayment.swift │ │ │ │ ├── OrderItem.swift │ │ │ │ └── Order.swift │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ ├── ProcessingController.swift │ │ │ └── OrderController.swift │ │ ├── Errors │ │ │ └── OrderError.swift │ │ ├── app.swift │ │ ├── routes.swift │ │ ├── Migrations │ │ │ ├── CreateOrderPayment.swift │ │ │ ├── CreateOrderItem.swift │ │ │ └── CreateOrder.swift │ │ ├── configure.swift │ │ ├── boot.swift │ │ └── JWTMiddleware.swift │ └── Run │ │ └── main.swift │ ├── .env │ ├── .dockerignore │ ├── .gitignore │ ├── circle.yml │ ├── docker-compose.yml │ ├── README.md │ ├── Package.swift │ └── web.Dockerfile ├── Chapter 14 └── ExampleVaporApp │ ├── Public │ └── .gitkeep │ ├── Tests │ ├── .gitkeep │ └── AppTests │ │ └── AppTests.swift │ ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── .gitkeep │ │ │ └── Todo.swift │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ └── TodoController.swift │ │ ├── routes.swift │ │ ├── Migrations │ │ │ └── CreateTodo.swift │ │ └── configure.swift │ └── Run │ │ └── main.swift │ ├── app copy.yaml │ ├── .github │ ├── FUNDING.yml │ └── workflows │ │ └── test.yml │ ├── token │ ├── .env │ ├── .dockerignore │ ├── cloud.yml │ ├── .gitignore │ ├── app.yaml │ ├── docker-compose.yml │ ├── taskdefinition.json │ ├── Dockerfile │ ├── Package.swift │ ├── README.md │ └── Package.resolved ├── Chapter 1 └── README.MD ├── Chapter 4 └── README.md ├── Chapter 2 └── HelloWorld │ ├── .gitignore │ ├── main.swift │ └── Package.swift ├── Chapter 7 ├── UserService │ ├── .dockerignore │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── Inputs │ │ │ │ │ ├── PasswordInput.swift │ │ │ │ │ ├── RefreshTokenInput.swift │ │ │ │ │ ├── LoginInput.swift │ │ │ │ │ ├── EditUserInput.swift │ │ │ │ │ ├── AddressInput.swift │ │ │ │ │ └── NewUserInput.swift │ │ │ │ ├── Responses │ │ │ │ │ ├── EmailSuccessResponse.swift │ │ │ │ │ ├── RefreshTokenResponse.swift │ │ │ │ │ ├── UserSuccessResponse.swift │ │ │ │ │ ├── LoginResponse.swift │ │ │ │ │ ├── UserResponse.swift │ │ │ │ │ └── AddressResponse.swift │ │ │ │ ├── RefreshToken.swift │ │ │ │ └── Database │ │ │ │ │ ├── Address.swift │ │ │ │ │ └── User.swift │ │ │ ├── Routes.swift │ │ │ ├── Migrations │ │ │ │ ├── CreateUser.swift │ │ │ │ └── CreateAddress.swift │ │ │ ├── Configure.swift │ │ │ └── Controllers │ │ │ │ ├── AddressController.swift │ │ │ │ └── UserController.swift │ │ └── Run │ │ │ └── main.swift │ ├── .gitignore │ ├── .env_example │ ├── Tests │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Dockerfile │ ├── Package.swift │ └── docker-compose.yml └── UserService-Postgres │ ├── .dockerignore │ ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── Inputs │ │ │ │ ├── PasswordInput.swift │ │ │ │ ├── RefreshTokenInput.swift │ │ │ │ ├── LoginInput.swift │ │ │ │ ├── EditUserInput.swift │ │ │ │ ├── AddressInput.swift │ │ │ │ └── NewUserInput.swift │ │ │ ├── Responses │ │ │ │ ├── EmailSuccessResponse.swift │ │ │ │ ├── RefreshTokenResponse.swift │ │ │ │ ├── UserSuccessResponse.swift │ │ │ │ ├── LoginResponse.swift │ │ │ │ ├── UserResponse.swift │ │ │ │ └── AddressResponse.swift │ │ │ ├── RefreshToken.swift │ │ │ └── Database │ │ │ │ ├── Address.swift │ │ │ │ └── User.swift │ │ ├── Routes.swift │ │ ├── Migrations │ │ │ ├── CreateUser.swift │ │ │ └── CreateAddress.swift │ │ ├── Configure.swift │ │ └── Controllers │ │ │ ├── UserController.swift │ │ │ └── AddressController.swift │ └── Run │ │ └── main.swift │ ├── .gitignore │ ├── Tests │ └── AppTests │ │ └── AppTests.swift │ ├── Dockerfile │ ├── Package.swift │ └── docker-compose.yml ├── Chapter 6 └── Shop Backend │ └── template │ ├── Sources │ ├── App │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ └── TodoController.swift │ │ ├── Models │ │ │ ├── Input │ │ │ │ └── .gitkeep │ │ │ ├── Responses │ │ │ │ └── .gitkeep │ │ │ └── Database │ │ │ │ └── Todo.swift │ │ ├── Migrations │ │ │ └── CreateTodo.swift │ │ ├── Routes.swift │ │ └── Configure.swift │ └── Run │ │ └── main.swift │ ├── .dockerignore │ ├── .gitignore │ ├── Tests │ └── AppTests │ │ └── AppTests.swift │ ├── Dockerfile │ ├── Package.swift │ └── docker-compose.yml ├── Chapter 9 ├── ProductService │ ├── .dockerignore │ ├── .gitignore │ ├── Sources │ │ ├── App │ │ │ ├── Models │ │ │ │ ├── Inputs │ │ │ │ │ ├── CategoryInput.swift │ │ │ │ │ └── ProductInput.swift │ │ │ │ ├── Responses │ │ │ │ │ ├── CategoryResponse.swift │ │ │ │ │ └── ProductResponse.swift │ │ │ │ ├── Database │ │ │ │ │ ├── Category.swift │ │ │ │ │ └── Product.swift │ │ │ │ └── Payload.swift │ │ │ ├── Migrations │ │ │ │ ├── CreateCategory.swift │ │ │ │ └── CreateProduct.swift │ │ │ ├── Routes.swift │ │ │ ├── Configure.swift │ │ │ └── Controllers │ │ │ │ ├── CategoriesController.swift │ │ │ │ └── ProductController.swift │ │ └── Run │ │ │ └── main.swift │ ├── Tests │ │ └── AppTests │ │ │ └── AppTests.swift │ ├── Dockerfile │ ├── Package.swift │ └── docker-compose.yml └── ProductService-Postgres │ ├── .dockerignore │ ├── .gitignore │ ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── Inputs │ │ │ │ ├── CategoryInput.swift │ │ │ │ └── ProductInput.swift │ │ │ ├── Responses │ │ │ │ ├── CategoryResponse.swift │ │ │ │ └── ProductResponse.swift │ │ │ ├── Database │ │ │ │ ├── Category.swift │ │ │ │ └── Product.swift │ │ │ └── Payload.swift │ │ ├── Migrations │ │ │ ├── CreateCategory.swift │ │ │ └── CreateProduct.swift │ │ ├── Routes.swift │ │ ├── Configure.swift │ │ └── Controllers │ │ │ ├── CategoriesController.swift │ │ │ └── ProductController.swift │ └── Run │ │ └── main.swift │ ├── Tests │ └── AppTests │ │ └── AppTests.swift │ ├── Dockerfile │ └── Package.swift ├── Chapter 13 └── README:MD ├── Chapter 10 └── README.md ├── Chapter 16 └── README.md ├── Chapter 8 └── README.md ├── .gitignore ├── Chapter 12 ├── GenericFunctions.swift ├── Protocols.swift ├── StraightforwardNames.swift └── Extensions.swift ├── LICENSE └── README.md /Chapter 15/orders/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/orders/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/users/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/users/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/products/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/products/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 1/README.MD: -------------------------------------------------------------------------------- 1 | # This chapter has no code in github. # -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 4/README.md: -------------------------------------------------------------------------------- 1 | # This chapter has no code in github. # -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 2/HelloWorld/.gitignore: -------------------------------------------------------------------------------- 1 | Package.resolved 2 | .build 3 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 3/HelloWorld/.gitignore: -------------------------------------------------------------------------------- 1 | Package.resolved 2 | .build 3 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 7/UserService/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Models/Input/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Chapter 13/README:MD: -------------------------------------------------------------------------------- 1 | # This chapter requires no code right now. # 2 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Models/Responses/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/app copy.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tanner0101] 2 | open_collective: vapor 3 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | try app(.detect()).run() 4 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tanner0101] 2 | open_collective: vapor 3 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | *.xcodeproj 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tanner0101] 2 | open_collective: vapor 3 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/token: -------------------------------------------------------------------------------- 1 | 75be48806b65dafdcd81716ca0c6c6747acc3d9a60909b40ede30fdc554e9bad 2 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | try app(.detect()).run() 4 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/.env: -------------------------------------------------------------------------------- 1 | DB_URL=postgres://vapor_username:vapor_password@localhost:5432/vapor_database 2 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | *.xcodeproj 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/.env: -------------------------------------------------------------------------------- 1 | DB_URL=postgres://vapor_username:vapor_password@localhost:5432/vapor_database 2 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/.env: -------------------------------------------------------------------------------- 1 | DB_URL=postgres://vapor_username:vapor_password@localhost:5432/vapor_database 2 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/.env: -------------------------------------------------------------------------------- 1 | DB_URL=postgres://vapor_username:vapor_password@localhost:5432/vapor_database 2 | -------------------------------------------------------------------------------- /Chapter 10/README.md: -------------------------------------------------------------------------------- 1 | # Hands-On-Microservices-with-Swift-5 2 | Hands-On Microservices with Swift 5, published by Packt 3 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | Package.resolved 5 | *.xcodeproj 6 | .swiftpm 7 | -------------------------------------------------------------------------------- /Chapter 16/README.md: -------------------------------------------------------------------------------- 1 | # Hands-On-Microservices-with-Swift-5 2 | Hands-On Microservices with Swift 5, published by Packt 3 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | Package.resolved 5 | *.xcodeproj 6 | 7 | -------------------------------------------------------------------------------- /Chapter 8/README.md: -------------------------------------------------------------------------------- 1 | # Hands-On-Microservices-with-Swift-5 2 | Hands-On Microservices with Swift 5, published by Packt 3 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | Package.resolved 5 | *.xcodeproj 6 | .swiftpm 7 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "4.1.0" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | .env 10 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "4.1.0" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "4.1.0" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "5.1.1" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | .swiftpm 8 | db.sqlite 9 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/PasswordInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct PasswordInput: Content { 4 | let email: String 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Input/Product.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct Product: Content { 4 | var id: Int 5 | var unitPrice: Int 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/PasswordInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct PasswordInput: Content { 4 | let email: String 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Inputs/CategoryInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct CategoryInput: Content { 4 | let name: String 5 | } 6 | 7 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/RefreshTokenInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct RefreshTokenInput: Content { 4 | let refreshToken: String 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Inputs/CategoryInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct CategoryInput: Content { 4 | let name: String 5 | } 6 | 7 | -------------------------------------------------------------------------------- /Chapter 2/HelloWorld/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | let app = try Application() 3 | 4 | app.get("hello") { req in 5 | return "Hello, world." 6 | } 7 | 8 | try app.run() 9 | -------------------------------------------------------------------------------- /Chapter 3/HelloWorld/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | let app = try Application() 3 | 4 | app.get("hello") { req in 5 | return "Hello, world." 6 | } 7 | 8 | try app.run() 9 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Output/AddedPaymentResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class AddedPaymentResponse: Content { 4 | var status = "success" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/RefreshTokenInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct RefreshTokenInput: Content { 4 | let refreshToken: String 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/LoginInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct LoginInput: Content { 4 | let email: String 5 | let password: String 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import AppTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += AppTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/EmailSuccessResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct EmailSuccessResponse: Content { 4 | let status: String = "success" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/LoginInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct LoginInput: Content { 4 | let email: String 5 | let password: String 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/EditUserInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct EditUserInput: Content { 4 | let firstname: String? 5 | let lastname: String? 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Responses/CategoryResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct CategoryResponse: Content { 4 | let id: Int 5 | let name: String 6 | } 7 | 8 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/EmailSuccessResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct EmailSuccessResponse: Content { 4 | let status: String = "success" 5 | } 6 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/EditUserInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct EditUserInput: Content { 4 | let firstname: String? 5 | let lastname: String? 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Responses/CategoryResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct CategoryResponse: Content { 4 | let id: Int 5 | let name: String 6 | } 7 | 8 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/AddressInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct AddressInput: Content { 4 | let street: String 5 | let city: String 6 | let zip: String 7 | } 8 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Input/PaymentInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct PaymentInput: Content { 4 | var orderId: Int 5 | var method: String 6 | var totalAmount: Int 7 | } 8 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/boot.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Called after your application has initialized. 4 | public func boot(_ app: Application) throws { 5 | // Your code here 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/RefreshTokenResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct RefreshTokenResponse: Content { 4 | let status = "success" 5 | let accessToken: String 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Input/OrderItemInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct OrderItemInput: Content { 4 | var productId: Int 5 | var unitPrice: Int 6 | var quantity: Int 7 | } 8 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/AddressInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct AddressInput: Content { 4 | let street: String 5 | let city: String 6 | let zip: String 7 | } 8 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/UserSuccessResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct UserSuccessResponse: Content { 4 | let status: String = "success" 5 | let user: UserResponse 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/RefreshTokenResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct RefreshTokenResponse: Content { 4 | let status = "success" 5 | let accessToken: String 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Errors/OrderError.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | enum OrderError: Error { 4 | case totalsNotMatching 5 | case paymentMissing 6 | case overpayment 7 | case orderNotFound 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | service: testservice 4 | instance_class: F1 5 | automatic_scaling: 6 | max_instances: 10 7 | min_instances: 1 8 | target_cpu_utilization: 0.6 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/UserSuccessResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct UserSuccessResponse: Content { 4 | let status: String = "success" 5 | let user: UserResponse 6 | } 7 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Inputs/ProductInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct ProductInput: Content { 4 | let name: String 5 | let description: String 6 | let price: Int 7 | let categoryId: Int 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Responses/ProductResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct ProductResponse: Content { 4 | let id: Int 5 | let name: String 6 | let description: String 7 | let price: Int 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Inputs/NewUserInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct NewUserInput: Content { 4 | let firstname: String? 5 | let lastname: String? 6 | let email: String 7 | let password: String 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Output/OrderItemResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct OrderItemResponse: Content { 4 | var productId: Int 5 | var unitPrice: Int 6 | var quantity: Int 7 | var totalAmount: Int 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 12/GenericFunctions.swift: -------------------------------------------------------------------------------- 1 | function saveModel(_ model: T, on app: Application) -> EventLoopFuture { 2 | return model.save(on: app).transform(to: model) 3 | } 4 | 5 | let user = User() 6 | return self.saveModel(user, on: app) 7 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Inputs/ProductInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct ProductInput: Content { 4 | let name: String 5 | let description: String 6 | let price: Int 7 | let categoryId: Int 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Responses/ProductResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct ProductResponse: Content { 4 | let id: Int 5 | let name: String 6 | let description: String 7 | let price: Int 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import XCTest 3 | 4 | final class AppTests: XCTestCase { 5 | func testNothing() throws { 6 | // add your tests here 7 | XCTAssert(true) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Inputs/NewUserInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct NewUserInput: Content { 4 | let firstname: String? 5 | let lastname: String? 6 | let email: String 7 | let password: String 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/LoginResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct LoginResponse: Content { 4 | let status = "success" 5 | let accessToken: String 6 | let refreshToken: String 7 | let user: UserResponse 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/LoginResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct LoginResponse: Content { 4 | let status = "success" 5 | let accessToken: String 6 | let refreshToken: String 7 | let user: UserResponse 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 15/users/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | } 7 | 8 | extension DatabaseID { 9 | static var test: Self { 10 | .init(string: "test") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Controllers/NewController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct NewController { 5 | func newFunction(req: Request) throws -> EventLoopFuture { 6 | return req.eventLoop.makeSucceededFuture(.ok) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter 7/UserService/.env_example: -------------------------------------------------------------------------------- 1 | JWKS={"keys":[{"kty":"RSA","d":"<< insert d here >>","e":"AQAB","use":"sig","kid":"backend","alg":"RS256","n":"<< insert n here >>"}]} 2 | MYSQL_CRED=mysql://vapor_username:vapor_username@localhost:3306/vapor_database?ssl=false 3 | SENDGRID_API_KEY=asdf 4 | -------------------------------------------------------------------------------- /Chapter 15/orders/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | 7 | } 8 | 9 | extension DatabaseID { 10 | static var test: Self { 11 | .init(string: "test") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter 15/products/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | 7 | } 8 | 9 | extension DatabaseID { 10 | static var test: Self { 11 | .init(string: "test") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: postgres:11 6 | environment: 7 | POSTGRES_USER: vapor_username 8 | POSTGRES_DB: vapor_database 9 | POSTGRES_PASSWORD: vapor_password 10 | ports: 11 | - 5432:5432 12 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: postgres:11 6 | environment: 7 | POSTGRES_USER: vapor_username 8 | POSTGRES_DB: vapor_database 9 | POSTGRES_PASSWORD: vapor_password 10 | ports: 11 | - 5432:5432 12 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: postgres:11 6 | environment: 7 | POSTGRES_USER: vapor_username 8 | POSTGRES_DB: vapor_database 9 | POSTGRES_PASSWORD: vapor_password 10 | ports: 11 | - 5432:5432 12 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Input/OrderInput.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct OrderInput: Content { 4 | var totalAmount: Int 5 | var firstname: String 6 | var lastname: String 7 | var street: String 8 | var zip: String 9 | var city: String 10 | var items:[OrderItemInput] 11 | } 12 | -------------------------------------------------------------------------------- /Chapter 12/Protocols.swift: -------------------------------------------------------------------------------- 1 | protocol HasEmail { 2 | var email: String { get set } 3 | } 4 | struct User: Model, HasEmail { 5 | var email: String 6 | } 7 | 8 | func checkEmailFor(model: HasEmail) { 9 | if model.email == "" { 10 | return false 11 | } else { 12 | return true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import XCTest 3 | 4 | final class AppTests: XCTestCase { 5 | func testNothing() throws { 6 | // Add your tests here 7 | XCTAssert(true) 8 | } 9 | 10 | static let allTests = [ 11 | ("testNothing", testNothing) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/app.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | public func app(_ environment: Environment) throws -> Application { 4 | var environment = environment 5 | try LoggingSystem.bootstrap(from: &environment) 6 | let app = Application(environment) 7 | try configure(app) 8 | try boot(app) 9 | return app 10 | } 11 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 12/StraightforwardNames.swift: -------------------------------------------------------------------------------- 1 | class ControllerA { 2 | routeA() -> EventLoopFuture { 3 | var f = "John" 4 | var l = "Cooper" 5 | // ... 6 | return b 7 | } 8 | } 9 | 10 | class UserController { 11 | routeA() -> EventLoopFuture { 12 | var firstname = "John" 13 | var lastname = "Cooper" 14 | // ... 15 | return update 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Database/Category.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Category: Model { 5 | 6 | static let schema = "categories" 7 | 8 | @ID(custom: "id") 9 | var id: Int? 10 | 11 | @Field(key: "name") 12 | var name: String 13 | 14 | init() { 15 | } 16 | 17 | init(name: String) { 18 | self.name = name 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Database/Category.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Category: Model { 5 | 6 | static let schema = "categories" 7 | 8 | @ID(custom: "id") 9 | var id: Int? 10 | 11 | @Field(key: "name") 12 | var name: String 13 | 14 | init() { 15 | } 16 | 17 | init(name: String) { 18 | self.name = name 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Models/Example.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Example: Model, Content { 5 | static let schema = "examples" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: Int? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Models/Database/Todo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Todo: Model, Content { 5 | static let schema = "todos" 6 | 7 | @ID(key: .id) 8 | var id: UUID? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | init() { } 14 | 15 | init(id: UUID? = nil, title: String) { 16 | self.id = id 17 | self.title = title 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 12/Extensions.swift: -------------------------------------------------------------------------------- 1 | func checkEmail(email: String) -> Bool { 2 | // ... 3 | return true 4 | } 5 | var testString = "test@test.com" 6 | if checkEmail(email: testString) { 7 | print("yep") 8 | } 9 | 10 | 11 | // Cleaner: 12 | 13 | extension String { 14 | func checkEmail() -> Bool { 15 | // ... 16 | return true 17 | } 18 | } 19 | var testString = "test@test.com" 20 | if testString.checkEmail() { 21 | print("yep") 22 | } 23 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello") { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello") { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .id() 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello") { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello") { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello") { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("health") { req in 10 | return "All good!" 11 | } 12 | 13 | let todoController = TodoController() 14 | app.get("todos", use: todoController.index) 15 | app.post("todos", use: todoController.create) 16 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("health") { req in 10 | return "All good!" 11 | } 12 | 13 | let todoController = TodoController() 14 | app.get("todos", use: todoController.index) 15 | app.post("todos", use: todoController.create) 16 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("health") { req in 10 | return "All good!" 11 | } 12 | 13 | let todoController = TodoController() 14 | app.get("todos", use: todoController.index) 15 | app.post("todos", use: todoController.create) 16 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/app.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Creates an instance of `Application`. This is called from `main.swift` in the run target. 4 | public func app(_ env: Environment) throws -> Application { 5 | var config = Config.default() 6 | var env = env 7 | var services = Services.default() 8 | try configure(&config, &env, &services) 9 | let app = try Application(config: config, environment: env, services: services) 10 | try boot(app) 11 | return app 12 | } 13 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("hello") { req in 10 | return "Hello, world!" 11 | } 12 | 13 | let todoController = TodoController() 14 | app.get("todos", use: todoController.index) 15 | app.post("todos", use: todoController.create) 16 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Tests/AppTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | #if !canImport(ObjectiveC) 2 | import XCTest 3 | 4 | extension AppTests { 5 | // DO NOT MODIFY: This is autogenerated, use: 6 | // `swift test --generate-linuxmain` 7 | // to regenerate. 8 | static let __allTests__AppTests = [ 9 | ("testNothing", testNothing), 10 | ] 11 | } 12 | 13 | public func __allTests() -> [XCTestCaseEntry] { 14 | return [ 15 | testCase(AppTests.__allTests__AppTests), 16 | ] 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("health") { req in 10 | return "All good!" 11 | } 12 | 13 | let todoController = TodoController() 14 | app.get("todos", use: todoController.index) 15 | app.post("todos", use: todoController.create) 16 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Migrations/CreateTodo.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateTodo: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("todos") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("todos").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/UserResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct UserResponse: Content { 4 | let id: Int? 5 | let firstname, lastname: String? 6 | let email: String 7 | let addresses: [AddressResponse]? 8 | 9 | init(user: User, addresses: [AddressResponse]? = nil) { 10 | self.id = user.id 11 | self.firstname = user.firstname 12 | self.lastname = user.lastname 13 | self.email = user.email 14 | self.addresses = addresses 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Migrations/CreateExample.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateExample: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("examples") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("title", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) -> EventLoopFuture { 12 | return database.schema("examples").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/UserResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct UserResponse: Content { 4 | let id: Int? 5 | let firstname, lastname: String? 6 | let email: String 7 | let addresses: [AddressResponse]? 8 | 9 | init(user: User, addresses: [AddressResponse]? = nil) { 10 | self.id = user.id 11 | self.firstname = user.firstname 12 | self.lastname = user.lastname 13 | self.email = user.email 14 | self.addresses = addresses 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Payload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vapor 3 | import JWTKit 4 | 5 | struct Payload: Content, JWTPayload { 6 | 7 | let firstname: String? 8 | let lastname: String? 9 | let exp: String 10 | let iat: String 11 | let email: String 12 | let id: Int 13 | 14 | func verify(using signer: JWTSigner) throws { 15 | let expiration = Date(timeIntervalSince1970: Double(self.exp)!) 16 | try ExpirationClaim(value: expiration).verifyNotExpired() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - pull_request 4 | jobs: 5 | xenial: 6 | container: 7 | image: vapor/swift:5.1-xenial 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - run: swift test --enable-test-discovery --sanitize=thread 12 | bionic: 13 | container: 14 | image: vapor/swift:5.1-bionic 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v1 18 | - run: swift test --enable-test-discovery --sanitize=thread 19 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Migrations/CreateCategory.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | 4 | struct CreateCategory: Migration { 5 | func prepare(on database: Database) -> EventLoopFuture { 6 | return database.schema("categories") 7 | .field("id", .int, .identifier(auto: true)) 8 | .field("name", .string, .required) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) -> EventLoopFuture { 13 | return database.schema("categories").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - pull_request 4 | jobs: 5 | xenial: 6 | container: 7 | image: vapor/swift:5.1-xenial 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - run: swift test --enable-test-discovery --sanitize=thread 12 | bionic: 13 | container: 14 | image: vapor/swift:5.1-bionic 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v1 18 | - run: swift test --enable-test-discovery --sanitize=thread 19 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | - pull_request 4 | jobs: 5 | xenial: 6 | container: 7 | image: vapor/swift:5.1-xenial 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - run: swift test --enable-test-discovery --sanitize=thread 12 | bionic: 13 | container: 14 | image: vapor/swift:5.1-bionic 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v1 18 | - run: swift test --enable-test-discovery --sanitize=thread 19 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Payload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vapor 3 | import JWTKit 4 | 5 | struct Payload: Content, JWTPayload { 6 | 7 | let firstname: String? 8 | let lastname: String? 9 | let exp: String 10 | let iat: String 11 | let email: String 12 | let id: Int 13 | 14 | func verify(using signer: JWTSigner) throws { 15 | let expiration = Date(timeIntervalSince1970: Double(self.exp)!) 16 | try ExpirationClaim(value: expiration).verifyNotExpired() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Migrations/CreateCategory.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | 4 | struct CreateCategory: Migration { 5 | func prepare(on database: Database) -> EventLoopFuture { 6 | return database.schema("categories") 7 | .field("id", .int, .identifier(auto: true)) 8 | .field("name", .string, .required) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) -> EventLoopFuture { 13 | return database.schema("categories").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Payload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vapor 3 | import JWTKit 4 | 5 | struct Payload: Content, JWTPayload { 6 | 7 | let firstname: String? 8 | let lastname: String? 9 | let exp: String 10 | let iat: String 11 | let email: String 12 | let id: Int 13 | 14 | func verify(using signer: JWTSigner) throws { 15 | let expiration = Date(timeIntervalSince1970: Double(self.exp)!) 16 | try ExpirationClaim(value: expiration).verifyNotExpired() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter 2/HelloWorld/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "App", targets: ["App"]), 11 | ], 12 | dependencies: [ 13 | // 💧 A server-side Swift web framework. 14 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: ["Vapor"], path: ".") 18 | ] 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /Chapter 3/HelloWorld/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "App", targets: ["App"]), 11 | ], 12 | dependencies: [ 13 | // 💧 A server-side Swift web framework. 14 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: ["Vapor"], path: ".") 18 | ] 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | import SendGrid 4 | import SimpleJWTMiddleware 5 | 6 | func routes(_ app: Application) throws { 7 | let root = app.grouped(.anything, "users") 8 | let auth = root.grouped(SimpleJWTMiddleware()) 9 | 10 | root.get("health") { request in 11 | return "All good!" 12 | } 13 | 14 | try auth.grouped("addresses").register(collection: AddressController()) 15 | try root.register(collection: AuthController()) 16 | try auth.register(collection: UserController()) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | import SendGrid 4 | import SimpleJWTMiddleware 5 | 6 | func routes(_ app: Application) throws { 7 | let root = app.grouped(.anything, "users") 8 | let auth = root.grouped(SimpleJWTMiddleware()) 9 | 10 | root.get("health") { request in 11 | return "All good!" 12 | } 13 | 14 | try auth.grouped("addresses").register(collection: AddressController()) 15 | try root.register(collection: AuthController()) 16 | try auth.register(collection: UserController()) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // Called before your application initializes. 6 | public func configure(_ app: Application) throws { 7 | // Serves files from `Public/` directory 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | // Configure SQLite database 11 | app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | // Configure migrations 14 | app.migrations.add(CreateTodo()) 15 | 16 | try routes(app) 17 | } 18 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Vapor API Template 2 | 3 | If you found a mistake or think of a cool new feature, please [create an issue](https://github.com/vapor/api-template/issues/new) or, if you want to implement it yourself, [fork this repo](https://github.com/vapor/api-template/fork) and open a Pull Request! 4 | 5 | We'll take a look as soon as we can. 6 | 7 | Thanks! 8 | 9 | ## Maintainers 10 | 11 | - [@0xTim](https://github.com/0xTim) 12 | 13 | See the [Vapor maintainers doc](https://github.com/vapor/vapor/blob/master/Docs/maintainers.md) for more information. 14 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("hello") { req in 10 | return "Hello, world!" 11 | } 12 | 13 | let todoController = TodoController() 14 | let newController = NewController() 15 | app.get("todos", use: todoController.index) 16 | app.post("todos", use: todoController.create) 17 | app.get("example", use: newController.index) 18 | app.on(.DELETE, "todos", ":todoID", use: todoController.delete) 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get([.anything, "name", "health"]) { req in 6 | return "Healthy!" 7 | } 8 | 9 | app.get { req in 10 | return "It works!" 11 | } 12 | 13 | app.get("hello") { req -> String in 14 | return "Hello, world!" 15 | } 16 | 17 | let todoController = TodoController() 18 | app.get("todos", use: todoController.index) 19 | app.post("todos", use: todoController.create) 20 | app.delete("todos", ":todoID", use: todoController.delete) 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | 6 | let currentVersion = app.grouped(.anything, "orders") 7 | 8 | currentVersion.get("health") { req in 9 | return "All good." 10 | } 11 | 12 | let protected = currentVersion.grouped(JWTMiddleware()) 13 | 14 | let orderController = OrderController() 15 | protected.post("", use: orderController.post) 16 | protected.get("", use: orderController.listMine) 17 | protected.get("all", use: orderController.list) 18 | protected.post("payment", ":id", use: orderController.postPayment) 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/RefreshToken.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import JWT 3 | 4 | struct RefreshToken: JWTPayload { 5 | let id: Int 6 | let iat: TimeInterval 7 | let exp: TimeInterval 8 | 9 | init(user: User, expiration: TimeInterval = 24 * 60 * 60 * 30) { 10 | let now = Date().timeIntervalSince1970 11 | 12 | self.id = user.id ?? 0 13 | self.iat = now 14 | self.exp = now + expiration 15 | } 16 | 17 | func verify(using signer: JWTSigner) throws { 18 | let expiration = Date(timeIntervalSince1970: self.exp) 19 | try ExpirationClaim(value: expiration).verifyNotExpired() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | bionic: 5 | docker: 6 | - image: vapor/swift:5.1-bionic 7 | steps: 8 | - checkout 9 | - run: apt-get update; apt-get install -y libssl-dev zlib1g-dev sqlite3 libsqlite3-dev 10 | - run: swift build 11 | - run: swift test 12 | bionic-release: 13 | docker: 14 | - image: vapor/swift:5.1-bionic 15 | steps: 16 | - checkout 17 | - run: apt-get update; apt-get install -y libssl-dev zlib1g-dev sqlite3 libsqlite3-dev 18 | - run: swift build -c release 19 | 20 | workflows: 21 | version: 2 22 | tests: 23 | jobs: 24 | - bionic 25 | - bionic-release 26 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | func testCreateTodo() throws { 7 | let app = Application(.testing) 8 | defer { app.shutdown() } 9 | try configure(app) 10 | 11 | // replace default database with in-memory db for testing 12 | app.databases.use(.sqlite(.memory), as: .test, isDefault: true) 13 | // run migrations automatically 14 | try app.autoMigrate().wait() 15 | 16 | 17 | } 18 | } 19 | 20 | extension DatabaseID { 21 | static var test: Self { 22 | .init(string: "test") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Register your application's routes here. 4 | public func routes(_ router: Router) throws { 5 | // Basic "It works" example 6 | router.get { req in 7 | return "It works!" 8 | } 9 | 10 | // Basic "Hello, world!" example 11 | router.get("hello") { req in 12 | return "Hello, world!" 13 | } 14 | 15 | // Example of configuring a controller 16 | let todoController = TodoController() 17 | router.get("todos", use: todoController.index) 18 | router.post("todos", use: todoController.create) 19 | router.delete("todos", Todo.parameter, use: todoController.delete) 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/RefreshToken.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import JWT 3 | 4 | struct RefreshToken: JWTPayload { 5 | let id: Int 6 | let iat: TimeInterval 7 | let exp: TimeInterval 8 | 9 | init(user: User, expiration: TimeInterval = 24 * 60 * 60 * 30) { 10 | let now = Date().timeIntervalSince1970 11 | 12 | self.id = user.id ?? 0 13 | self.iat = now 14 | self.exp = now + expiration 15 | } 16 | 17 | func verify(using signer: JWTSigner) throws { 18 | let expiration = Date(timeIntervalSince1970: self.exp) 19 | try ExpirationClaim(value: expiration).verifyNotExpired() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Responses/AddressResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class AddressResponse: Content { 4 | var id: Int? 5 | var street: String 6 | var city: String 7 | var zip: String 8 | var userId: Int 9 | var createdAt: Date? 10 | var updatedAt: Date? 11 | var deletedAt: Date? 12 | 13 | init(_ address: Address) { 14 | self.id = address.id 15 | self.street = address.street 16 | self.city = address.city 17 | self.zip = address.zip 18 | self.userId = address.userId 19 | self.createdAt = address.createdAt 20 | self.updatedAt = address.updatedAt 21 | self.deletedAt = address.deletedAt 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Responses/AddressResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class AddressResponse: Content { 4 | var id: Int? 5 | var street: String 6 | var city: String 7 | var zip: String 8 | var userId: Int 9 | var createdAt: Date? 10 | var updatedAt: Date? 11 | var deletedAt: Date? 12 | 13 | init(_ address: Address) { 14 | self.id = address.id 15 | self.street = address.street 16 | self.city = address.city 17 | self.zip = address.zip 18 | self.userId = address.userId 19 | self.createdAt = address.createdAt 20 | self.updatedAt = address.updatedAt 21 | self.deletedAt = address.deletedAt 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter 15/users/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .map { .ok } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 15/orders/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .map { .ok } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 15/products/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .map { .ok } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .map { .ok } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .map { .ok } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | return Todo.query(on: req.db).all() 7 | } 8 | 9 | func create(req: Request) throws -> EventLoopFuture { 10 | let todo = try req.content.decode(Todo.self) 11 | return todo.save(on: req.db).map { todo } 12 | } 13 | 14 | func delete(req: Request) throws -> EventLoopFuture { 15 | return Todo.find(req.parameters.get("todoID"), on: req.db) 16 | .unwrap(or: Abort(.notFound)) 17 | .flatMap { $0.delete(on: req.db) } 18 | .transform(to: .ok) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Compile with optimizations 11 | RUN swift build \ 12 | --enable-test-discovery \ 13 | -c release \ 14 | -Xswiftc -g 15 | 16 | # ================================ 17 | # Run image 18 | # ================================ 19 | FROM vapor/ubuntu:18.04 20 | WORKDIR /run 21 | 22 | # Copy build artifacts 23 | COPY --from=build /build/.build/release /run 24 | # Copy Swift runtime libraries 25 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 26 | 27 | ENTRYPOINT ["./Run"] 28 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] -------------------------------------------------------------------------------- /Chapter 9/ProductService/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Compile with optimizations 11 | RUN swift build \ 12 | --enable-test-discovery \ 13 | -c release \ 14 | -Xswiftc -g 15 | 16 | # ================================ 17 | # Run image 18 | # ================================ 19 | FROM vapor/ubuntu:18.04 20 | WORKDIR /run 21 | 22 | # Copy build artifacts 23 | COPY --from=build /build/.build/release /run 24 | # Copy Swift runtime libraries 25 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 26 | 27 | ENTRYPOINT ["./Run"] 28 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Compile with optimizations 11 | RUN swift build \ 12 | --enable-test-discovery \ 13 | -c release \ 14 | -Xswiftc -g 15 | 16 | # ================================ 17 | # Run image 18 | # ================================ 19 | FROM vapor/ubuntu:18.04 20 | WORKDIR /run 21 | 22 | # Copy build artifacts 23 | COPY --from=build /build/.build/release /run 24 | # Copy Swift runtime libraries 25 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 26 | 27 | ENTRYPOINT ["./Run"] 28 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Compile with optimizations 11 | RUN swift build \ 12 | --enable-test-discovery \ 13 | -c release \ 14 | -Xswiftc -g 15 | 16 | # ================================ 17 | # Run image 18 | # ================================ 19 | FROM vapor/ubuntu:18.04 20 | WORKDIR /run 21 | 22 | # Copy build artifacts 23 | COPY --from=build /build/.build/release /run 24 | # Copy Swift runtime libraries 25 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 26 | 27 | ENTRYPOINT ["./Run"] 28 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Compile with optimizations 11 | RUN swift build \ 12 | --enable-test-discovery \ 13 | -c release \ 14 | -Xswiftc -g 15 | 16 | # ================================ 17 | # Run image 18 | # ================================ 19 | FROM vapor/ubuntu:18.04 20 | WORKDIR /run 21 | 22 | # Copy build artifacts 23 | COPY --from=build /build/.build/release /run 24 | # Copy Swift runtime libraries 25 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 26 | 27 | ENTRYPOINT ["./Run"] 28 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0"] -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Migrations/CreateOrderPayment.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateOrderPayment: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("orderPayments") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("totalAmount", .int, .required) 8 | .field("method", .string, .required) 9 | .field("orderId", .int, .required) 10 | .field("createdAt", .datetime) 11 | .field("updatedAt", .datetime) 12 | .field("deletedAt", .datetime) 13 | .create() 14 | } 15 | 16 | func revert(on database: Database) -> EventLoopFuture { 17 | return database.schema("orderPayments").delete() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct TodoController { 5 | func index(req: Request) throws -> EventLoopFuture<[Todo]> { 6 | let page = req.query.get(Int.self, at: "page") 7 | return Todo.query(on: req.db).all() 8 | } 9 | 10 | func create(req: Request) throws -> EventLoopFuture { 11 | let todo = try req.content.decode(Todo.self) 12 | return todo.save(on: req.db).map { todo } 13 | } 14 | 15 | func delete(req: Request) throws -> EventLoopFuture { 16 | return Todo.find(req.parameters.get("todoID"), on: req.db) 17 | .unwrap(or: Abort(.notFound)) 18 | .flatMap { $0.delete(on: req.db) } 19 | .map { .ok } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Migrations/CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("users") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("firstname", .string) 8 | .field("lastname", .string) 9 | .field("email", .string, .required) 10 | .field("password", .string, .required) 11 | .field("createdAt", .datetime) 12 | .field("updatedAt", .datetime) 13 | .field("deletedAt", .datetime) 14 | .create() 15 | } 16 | 17 | func revert(on database: Database) -> EventLoopFuture { 18 | return database.schema("users").delete() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Migrations/CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("users") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("firstname", .string) 8 | .field("lastname", .string) 9 | .field("email", .string, .required) 10 | .field("password", .string, .required) 11 | .field("createdAt", .datetime) 12 | .field("updatedAt", .datetime) 13 | .field("deletedAt", .datetime) 14 | .create() 15 | } 16 | 17 | func revert(on database: Database) -> EventLoopFuture { 18 | return database.schema("users").delete() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "VaporApp", 6 | products: [ 7 | .library(name: "VaporApp", targets: ["App"]), 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), 12 | 13 | // 🔵 Swift ORM (queries, models, relations, etc) built on SQLite 3. 14 | .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: ["FluentSQLite", "Vapor"]), 18 | .target(name: "Run", dependencies: ["App"]), 19 | .testTarget(name: "AppTests", dependencies: ["App"]) 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "vapor-example-app", 3 | "executionRoleArn": "arn:aws:iam::426628247310:role/ecsTaskExecutionRole", 4 | "containerDefinitions": [ 5 | { 6 | "name": "vapor-example-app", 7 | "portMappings": [ 8 | { 9 | "hostPort": 8080, 10 | "protocol": "tcp", 11 | "containerPort": 8080 12 | } 13 | ], 14 | "essential": true, 15 | "environment": [ 16 | { 17 | "name": "EXAMPLE_VAR", 18 | "value": "configValue" 19 | } 20 | ], 21 | "image": "426628247310.dkr.ecr.us-east-1.amazonaws.com/examplevapor" 22 | } 23 | ], 24 | "memory": "512", 25 | "requiresCompatibilities": [ 26 | "FARGATE" 27 | ], 28 | "networkMode": "awsvpc", 29 | "cpu": "256" 30 | } 31 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Migrations/CreateAddress.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateAddress: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("addresses") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("userId", .int, .references("users", "id")) 8 | .field("street", .string, .required) 9 | .field("zip", .string, .required) 10 | .field("city", .string, .required) 11 | .field("createdAt", .datetime) 12 | .field("updatedAt", .datetime) 13 | .field("deletedAt", .datetime) 14 | .create() 15 | } 16 | 17 | func revert(on database: Database) -> EventLoopFuture { 18 | return database.schema("addresses").delete() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Migrations/CreateProduct.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | 4 | struct CreateProduct: Migration { 5 | func prepare(on database: Database) -> EventLoopFuture { 6 | return database.schema("products") 7 | .field("id", .int, .identifier(auto: true)) 8 | .field("name", .string, .required) 9 | .field("description", .string, .required) 10 | .field("categoryId", .int, .required) 11 | .field("price", .int, .required) 12 | .field("createdAt", .datetime) 13 | .field("updatedAt", .datetime) 14 | .field("deletedAt", .datetime) 15 | .create() 16 | } 17 | 18 | func revert(on database: Database) -> EventLoopFuture { 19 | return database.schema("products").delete() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Migrations/CreateProduct.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | 4 | struct CreateProduct: Migration { 5 | func prepare(on database: Database) -> EventLoopFuture { 6 | return database.schema("products") 7 | .field("id", .int, .identifier(auto: true)) 8 | .field("name", .string, .required) 9 | .field("description", .string, .required) 10 | .field("categoryId", .int, .required) 11 | .field("price", .int, .required) 12 | .field("createdAt", .datetime) 13 | .field("updatedAt", .datetime) 14 | .field("deletedAt", .datetime) 15 | .create() 16 | } 17 | 18 | func revert(on database: Database) -> EventLoopFuture { 19 | return database.schema("products").delete() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Migrations/CreateAddress.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateAddress: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("addresses") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("userId", .int, .references("users", "id")) 8 | .field("street", .string, .required) 9 | .field("zip", .string, .required) 10 | .field("city", .string, .required) 11 | .field("createdAt", .datetime) 12 | .field("updatedAt", .datetime) 13 | .field("deletedAt", .datetime) 14 | .create() 15 | } 16 | 17 | func revert(on database: Database) -> EventLoopFuture { 18 | return database.schema("addresses").delete() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | import FluentSQLite 2 | import Vapor 3 | 4 | /// A single entry of a Todo list. 5 | final class Todo: SQLiteModel { 6 | typealias Database = SQLiteDatabase 7 | /// The unique identifier for this `Todo`. 8 | var id: Int? 9 | 10 | /// A title describing what this `Todo` entails. 11 | var title: String 12 | 13 | /// Creates a new `Todo`. 14 | init(id: Int? = nil, title: String) { 15 | self.id = id 16 | self.title = title 17 | } 18 | } 19 | 20 | /// Allows `Todo` to be used as a dynamic migration. 21 | extension Todo: Migration { } 22 | 23 | /// Allows `Todo` to be encoded to and decoded from HTTP messages. 24 | extension Todo: Content { } 25 | 26 | /// Allows `Todo` to be used as a dynamic parameter in route definitions. 27 | extension Todo: Parameter { } 28 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/Controllers/TodoController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Controls basic CRUD operations on `Todo`s. 4 | final class TodoController { 5 | /// Returns a list of all `Todo`s. 6 | func index(_ req: Request) throws -> Future<[Todo]> { 7 | return Todo.query(on: req).all() 8 | } 9 | 10 | /// Saves a decoded `Todo` to the database. 11 | func create(_ req: Request) throws -> Future { 12 | return try req.content.decode(Todo.self).flatMap { todo in 13 | return todo.save(on: req) 14 | } 15 | } 16 | 17 | /// Deletes a parameterized `Todo`. 18 | func delete(_ req: Request) throws -> Future { 19 | return try req.parameters.next(Todo.self).flatMap { todo in 20 | return todo.delete(on: req) 21 | }.transform(to: .ok) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter 15/orders/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.1 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release 18 | 19 | # ================================ 20 | # Run image 21 | # ================================ 22 | FROM vapor/ubuntu:18.04 23 | WORKDIR /run 24 | 25 | # Copy build artifacts 26 | COPY --from=build /build/.build/release /run 27 | # Copy Swift runtime libraries 28 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 29 | 30 | EXPOSE 8080 31 | 32 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 33 | -------------------------------------------------------------------------------- /Chapter 15/products/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.1 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release 18 | 19 | # ================================ 20 | # Run image 21 | # ================================ 22 | FROM vapor/ubuntu:18.04 23 | WORKDIR /run 24 | 25 | # Copy build artifacts 26 | COPY --from=build /build/.build/release /run 27 | # Copy Swift runtime libraries 28 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 29 | 30 | EXPOSE 8080 31 | 32 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 33 | -------------------------------------------------------------------------------- /Chapter 15/users/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.1 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release 18 | 19 | # ================================ 20 | # Run image 21 | # ================================ 22 | FROM vapor/ubuntu:18.04 23 | WORKDIR /run 24 | 25 | # Copy build artifacts 26 | COPY --from=build /build/.build/release /run 27 | # Copy Swift runtime libraries 28 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 29 | 30 | EXPOSE 8080 31 | 32 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 33 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/web.Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release \ 18 | -Xswiftc -g 19 | 20 | # ================================ 21 | # Run image 22 | # ================================ 23 | FROM vapor/ubuntu:18.04 24 | WORKDIR /run 25 | 26 | # Copy build artifacts 27 | COPY --from=build /build/.build/release /run 28 | # Copy Swift runtime libraries 29 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 30 | 31 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 32 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/web.Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.2 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release \ 18 | -Xswiftc -g 19 | 20 | # ================================ 21 | # Run image 22 | # ================================ 23 | FROM vapor/ubuntu:18.04 24 | WORKDIR /run 25 | 26 | # Copy build artifacts 27 | COPY --from=build /build/.build/release /run 28 | # Copy Swift runtime libraries 29 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 30 | 31 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 32 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Migrations/CreateOrderItem.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateOrderItem: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("orderItems") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("totalAmount", .int, .required) 8 | .field("unitPrice", .int, .required) 9 | .field("productId", .int, .required) 10 | .field("quantity", .int, .required) 11 | .field("orderId", .int, .required) 12 | .field("createdAt", .datetime) 13 | .field("updatedAt", .datetime) 14 | .field("deletedAt", .datetime) 15 | .create() 16 | } 17 | 18 | func revert(on database: Database) -> EventLoopFuture { 19 | return database.schema("orderItems").delete() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM vapor/swift:5.1 as build 5 | WORKDIR /build 6 | 7 | # Copy entire repo into container 8 | COPY . . 9 | 10 | # Install sqlite3 11 | RUN apt-get update -y \ 12 | && apt-get install -y libsqlite3-dev 13 | 14 | # Compile with optimizations 15 | RUN swift build \ 16 | --enable-test-discovery \ 17 | -c release 18 | 19 | # ================================ 20 | # Run image 21 | # ================================ 22 | FROM vapor/ubuntu:18.04 23 | WORKDIR /run 24 | 25 | # Copy build artifacts 26 | COPY --from=build /build/.build/release /run 27 | # Copy Swift runtime libraries 28 | COPY --from=build /usr/lib/swift/ /usr/lib/swift/ 29 | 30 | EXPOSE 8080 31 | 32 | ENTRYPOINT ["./Run", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 33 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: mysql:8 6 | command: --default-authentication-plugin=mysql_native_password 7 | restart: always 8 | environment: 9 | MYSQL_DATABASE: 'db' 10 | # So you don't have to use root, but you can if you like 11 | MYSQL_USER: 'user' 12 | # You can use whatever password you like 13 | MYSQL_PASSWORD: 'password' 14 | # Password for root access 15 | MYSQL_ROOT_PASSWORD: 'password' 16 | ports: 17 | # : < MySQL Port running inside container> 18 | - '3306:3306' 19 | expose: 20 | # Opens port 3306 on the container 21 | - '3306' 22 | # Where our data will be persisted 23 | volumes: 24 | - my-db:/var/lib/mysql 25 | # Names our volume 26 | volumes: 27 | my-db: 28 | 29 | # export MYSQL_URL='mysql://user:password@localhost/db' -------------------------------------------------------------------------------- /Chapter 15/orders/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_14) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 15/products/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_14) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 15/users/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15 ) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "app", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 18 | ], 19 | targets: [ 20 | .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor"]), 21 | .target(name: "Run", dependencies: ["App"]), 22 | .testTarget(name: "AppTests", dependencies: ["App", "XCTVapor"]) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | linux: 5 | docker: 6 | - image: swift:4.1 7 | steps: 8 | - checkout 9 | - run: 10 | name: Compile code 11 | command: swift build 12 | - run: 13 | name: Run unit tests 14 | command: swift test 15 | 16 | linux-release: 17 | docker: 18 | - image: swift:4.1 19 | steps: 20 | - checkout 21 | - run: 22 | name: Compile code with optimizations 23 | command: swift build -c release 24 | 25 | workflows: 26 | version: 2 27 | tests: 28 | jobs: 29 | - linux 30 | - linux-release 31 | 32 | nightly: 33 | triggers: 34 | - schedule: 35 | cron: "0 0 * * *" 36 | filters: 37 | branches: 38 | only: 39 | - master 40 | jobs: 41 | - linux 42 | - linux-release 43 | 44 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Output/OrderResponse.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct OrderResponse: Content { 4 | var id: Int 5 | var totalAmount: Int 6 | var paidAmount: Int 7 | var userId: Int 8 | var status: Int 9 | var createdAt: Date? 10 | var items:[OrderItemResponse] 11 | 12 | init(order: Order, items: [OrderItem]) { 13 | self.id = order.id! 14 | self.totalAmount = order.totalAmount 15 | self.paidAmount = order.paidAmount 16 | self.userId = order.userId 17 | self.status = order.status 18 | self.createdAt = order.createdAt 19 | 20 | self.items = [] 21 | 22 | for item in items { 23 | let orderItemResponse = OrderItemResponse(productId: item.productId, unitPrice: item.unitPrice, quantity: item.quantity, totalAmount: item.totalAmount) 24 | self.items.append(orderItemResponse) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter 15/orders/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 |
6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 15/users/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Database/Address.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Address: Model { 5 | 6 | static let schema = "addresses" 7 | 8 | @ID(custom: "id") 9 | var id: Int? 10 | 11 | @Field(key: "street") 12 | var street: String 13 | 14 | @Field(key: "city") 15 | var city: String 16 | 17 | @Field(key: "zip") 18 | var zip: String 19 | 20 | @Field(key: "userId") 21 | var userId: Int 22 | 23 | @Timestamp(key: "createdAt", on: .create) 24 | var createdAt: Date? 25 | @Timestamp(key: "updatedAt", on: .update) 26 | var updatedAt: Date? 27 | @Timestamp(key: "deletedAt", on: .delete) 28 | var deletedAt: Date? 29 | 30 | init() { 31 | } 32 | 33 | init(street: String, city: String, zip: String, userId: Int) { 34 | self.street = street 35 | self.city = city 36 | self.zip = zip 37 | self.userId = userId 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 15/products/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 5.1 19 | 20 |

21 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Database/Address.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Address: Model { 5 | 6 | 7 | 8 | static let schema = "addresses" 9 | 10 | @ID(custom: "id") 11 | var id: Int? 12 | 13 | @Field(key: "street") 14 | var street: String 15 | 16 | @Field(key: "city") 17 | var city: String 18 | 19 | @Field(key: "zip") 20 | var zip: String 21 | 22 | @Field(key: "userId") 23 | var userId: Int 24 | 25 | @Timestamp(key: "createdAt", on: .create) 26 | var createdAt: Date? 27 | @Timestamp(key: "updatedAt", on: .update) 28 | var updatedAt: Date? 29 | @Timestamp(key: "deletedAt", on: .delete) 30 | var deletedAt: Date? 31 | 32 | init() { 33 | } 34 | 35 | init(street: String, city: String, zip: String, userId: Int) { 36 | self.street = street 37 | self.city = city 38 | self.zip = zip 39 | self.userId = userId 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | import SimpleJWTMiddleware 4 | 5 | func routes(_ app: Application) throws { 6 | let root = app.grouped(.anything, "products") 7 | let authorized = root.grouped(SimpleJWTMiddleware()) 8 | 9 | root.get("health") { request in 10 | return "All good!" 11 | } 12 | 13 | let productsController = ProductsController() 14 | let categoriesController = CategoriesController() 15 | 16 | root.get("categories", use: categoriesController.get) 17 | authorized.post("categories", use: categoriesController.new) 18 | authorized.patch("categories", ":id", use: categoriesController.edit) 19 | authorized.delete("categories", ":id", use: categoriesController.delete) 20 | 21 | root.get("", use: productsController.get) 22 | authorized.post("", use: productsController.new) 23 | authorized.patch(":id", use: productsController.edit) 24 | authorized.delete(":id", use: productsController.delete) 25 | } 26 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Database/OrderPayment.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class OrderPayment: Model, Content { 5 | static let schema = "orderPayments" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "totalAmount") 11 | var totalAmount: Int 12 | 13 | @Parent(key: "orderId") 14 | var order: Order 15 | 16 | @Field(key: "method") 17 | var method: String 18 | 19 | @Timestamp(key: "createdAt", on: .create) 20 | var createdAt: Date? 21 | 22 | @Timestamp(key: "updatedAt", on: .update) 23 | var updatedAt: Date? 24 | 25 | @Timestamp(key: "deletedAt", on: .delete) 26 | var deletedAt: Date? 27 | 28 | init() { } 29 | 30 | init( 31 | id: Int? = nil, 32 | totalAmount: Int, 33 | method: String, 34 | order: Order) { 35 | self.id = id 36 | self.totalAmount = totalAmount 37 | self.method = method 38 | self.order = order 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | import SimpleJWTMiddleware 4 | 5 | func routes(_ app: Application) throws { 6 | let root = app.grouped(.anything, "products") 7 | let authorized = root.grouped(SimpleJWTMiddleware()) 8 | 9 | root.get("health") { request in 10 | return "All good!" 11 | } 12 | 13 | let productsController = ProductsController() 14 | let categoriesController = CategoriesController() 15 | 16 | root.get("categories", use: categoriesController.get) 17 | authorized.post("categories", use: categoriesController.new) 18 | authorized.patch("categories", ":id", use: categoriesController.edit) 19 | authorized.delete("categories", ":id", use: categoriesController.delete) 20 | 21 | root.get("", use: productsController.get) 22 | authorized.post("", use: productsController.new) 23 | authorized.patch(":id", use: productsController.edit) 24 | authorized.delete(":id", use: productsController.delete) 25 | } 26 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "OrderService", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | products: [ 10 | .executable(name: "Run", targets: ["Run"]), 11 | .library(name: "App", targets: ["App"]), 12 | ], 13 | dependencies: [ 14 | // 💧 A server-side Swift web framework. 15 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 16 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 17 | .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"), 18 | .package(url: "https://github.com/vapor/jwt.git", .exact("4.0.0")), 19 | 20 | ], 21 | targets: [ 22 | .target(name: "App", dependencies: ["Fluent", "FluentMySQLDriver", "JWT", "Vapor"]), 23 | .target(name: "Run", dependencies: ["App"]), 24 | .testTarget(name: "AppTests", dependencies: ["App"]) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Models/Database/Product.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Product: Model { 5 | 6 | static let schema = "products" 7 | 8 | @ID(custom: "id") 9 | var id: Int? 10 | 11 | @Field(key: "name") 12 | var name: String 13 | 14 | @Field(key: "description") 15 | var description: String 16 | 17 | @Field(key: "price") 18 | var price: Int 19 | 20 | @Field(key: "categoryId") 21 | var categoryId: Int 22 | 23 | @Timestamp(key: "createdAt", on: .create) 24 | var createdAt: Date? 25 | 26 | @Timestamp(key: "updatedAt", on: .update) 27 | var updatedAt: Date? 28 | 29 | @Timestamp(key: "deletedAt", on: .delete) 30 | var deletedAt: Date? 31 | 32 | init() { 33 | } 34 | 35 | init(name: String, description: String, price: Int, categoryId: Int) { 36 | self.name = name 37 | self.description = description 38 | self.price = price 39 | self.categoryId = categoryId 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Models/Database/Product.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Product: Model { 5 | 6 | static let schema = "products" 7 | 8 | @ID(custom: "id") 9 | var id: Int? 10 | 11 | @Field(key: "name") 12 | var name: String 13 | 14 | @Field(key: "description") 15 | var description: String 16 | 17 | @Field(key: "price") 18 | var price: Int 19 | 20 | @Field(key: "categoryId") 21 | var categoryId: Int 22 | 23 | @Timestamp(key: "createdAt", on: .create) 24 | var createdAt: Date? 25 | 26 | @Timestamp(key: "updatedAt", on: .update) 27 | var updatedAt: Date? 28 | 29 | @Timestamp(key: "deletedAt", on: .delete) 30 | var deletedAt: Date? 31 | 32 | init() { 33 | } 34 | 35 | init(name: String, description: String, price: Int, categoryId: Int) { 36 | self.name = name 37 | self.description = description 38 | self.price = price 39 | self.categoryId = categoryId 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Migrations/CreateOrder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateOrder: Migration { 4 | func prepare(on database: Database) -> EventLoopFuture { 5 | return database.schema("orders") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("totalAmount", .int, .required) 8 | .field("paidAmount", .int, .required) 9 | .field("userId", .int, .required) 10 | .field("status", .int, .required) 11 | .field("firstname", .string, .required) 12 | .field("lastname", .string, .required) 13 | .field("street", .string, .required) 14 | .field("zip", .string, .required) 15 | .field("city", .string, .required) 16 | .field("createdAt", .datetime) 17 | .field("updatedAt", .datetime) 18 | .field("deletedAt", .datetime) 19 | .create() 20 | } 21 | 22 | func revert(on database: Database) -> EventLoopFuture { 23 | return database.schema("orders").delete() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Sources/App/Configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentMySQLDriver 3 | import Vapor 4 | import JWT 5 | 6 | // configures your application 7 | public func configure(_ app: Application) throws { 8 | // uncomment to serve files from /Public folder 9 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 10 | 11 | app.middleware.use(CORSMiddleware()) 12 | 13 | guard let jwksString = Environment.process.JWKS else { fatalError("No value was found at the given public key environment 'JWKS'") 14 | } 15 | 16 | guard let mysqlUrl = Environment.process.MYSQL_CRED else { fatalError("No value was found at the given public key environment 'MYSQL_CRED'") 17 | } 18 | guard let url = URL(string: mysqlUrl) else { fatalError("Cannot parse: \(mysqlUrl) correctly.") 19 | } 20 | app.databases.use(try .mysql(url: url), as: .mysql) 21 | 22 | try app.jwt.signers.use(jwksJSON: jwksString) 23 | 24 | 25 | 26 | app.migrations.add(CreateTodo()) 27 | 28 | // register routes 29 | try routes(app) 30 | } 31 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/web.Dockerfile: -------------------------------------------------------------------------------- 1 | # You can set the Swift version to what you need for your app. Versions can be found here: https://hub.docker.com/_/swift 2 | FROM swift:4.2 as builder 3 | 4 | # For local build, add `--build-arg environment=local` 5 | ARG env="" 6 | ENV ENVIRONMENT=$env 7 | 8 | RUN apt-get -qq update && apt-get -q -y install \ 9 | tzdata \ 10 | && rm -r /var/lib/apt/lists/* 11 | WORKDIR /app 12 | COPY . . 13 | RUN mkdir -p /build/lib && cp -R /usr/lib/swift/linux/*.so /build/lib 14 | RUN swift build -c release && mv `swift build -c release --show-bin-path` /build/bin 15 | 16 | # Production image 17 | FROM ubuntu:16.04 18 | RUN apt-get -qq update && apt-get install -y \ 19 | libicu55 libxml2 libbsd0 libcurl3 libatomic1 \ 20 | tzdata \ 21 | && rm -r /var/lib/apt/lists/* 22 | WORKDIR /app 23 | COPY --from=builder /build/bin/Run . 24 | COPY --from=builder /build/lib/* /usr/lib/ 25 | COPY --from=builder /app/Public ./Public 26 | # Uncommand the next line if you are using Leaf 27 | #COPY --from=builder /app/Resources ./Resources 28 | 29 | ENTRYPOINT ./Run serve --env $ENVIRONMENT --hostname 0.0.0.0 --port 80 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 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 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Models/Database/User.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class User: Model { 5 | 6 | static let schema = "users" 7 | 8 | init() { 9 | } 10 | 11 | @ID(custom: "id") 12 | var id: Int? 13 | 14 | @Field(key: "firstname") 15 | var firstname: String? 16 | 17 | @Field(key: "lastname") 18 | var lastname: String? 19 | 20 | @Field(key: "email") 21 | var email: String 22 | 23 | @Field(key: "password") 24 | var password: String 25 | 26 | @Timestamp(key: "createdAt", on: .create) 27 | var createdAt: Date? 28 | 29 | @Timestamp(key: "updatedAt", on: .update) 30 | var updatedAt: Date? 31 | 32 | @Timestamp(key: "deletedAt", on: .delete) 33 | var deletedAt: Date? 34 | 35 | init(_ email: String, _ firstName: String? = nil, _ lastName: String? = nil, _ password: String)throws { 36 | self.email = email 37 | 38 | self.firstname = firstName 39 | self.lastname = lastName 40 | self.password = try BCryptDigest().hash(password) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Models/Database/User.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class User: Model { 5 | 6 | static let schema = "users" 7 | 8 | init() { 9 | } 10 | 11 | @ID(custom: "id") 12 | var id: Int? 13 | 14 | @Field(key: "firstname") 15 | var firstname: String? 16 | 17 | @Field(key: "lastname") 18 | var lastname: String? 19 | 20 | @Field(key: "email") 21 | var email: String 22 | 23 | @Field(key: "password") 24 | var password: String 25 | 26 | @Timestamp(key: "createdAt", on: .create) 27 | var createdAt: Date? 28 | 29 | @Timestamp(key: "updatedAt", on: .update) 30 | var updatedAt: Date? 31 | 32 | @Timestamp(key: "deletedAt", on: .delete) 33 | var deletedAt: Date? 34 | 35 | init(_ email: String, _ firstName: String? = nil, _ lastName: String? = nil, _ password: String)throws { 36 | self.email = email 37 | 38 | self.firstname = firstName 39 | self.lastname = lastName 40 | self.password = try BCryptDigest().hash(password) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Database/OrderItem.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class OrderItem: Model, Content { 5 | static let schema = "orderItems" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "unitPrice") 11 | var unitPrice: Int 12 | 13 | @Field(key: "totalAmount") 14 | var totalAmount: Int 15 | 16 | @Field(key: "productId") 17 | var productId: Int 18 | 19 | @Field(key: "quantity") 20 | var quantity: Int 21 | 22 | @Parent(key: "orderId") 23 | var order: Order 24 | 25 | @Timestamp(key: "createdAt", on: .create) 26 | var createdAt: Date? 27 | 28 | @Timestamp(key: "updatedAt", on: .update) 29 | var updatedAt: Date? 30 | 31 | @Timestamp(key: "deletedAt", on: .delete) 32 | var deletedAt: Date? 33 | 34 | init() { } 35 | 36 | init( 37 | id: Int? = nil, 38 | totalAmount: Int, 39 | unitPrice: Int, 40 | quantity: Int, 41 | order: Order) { 42 | self.id = id 43 | self.totalAmount = totalAmount 44 | self.unitPrice = unitPrice 45 | self.quantity = quantity 46 | self.order = order 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentMySQLDriver 3 | import Vapor 4 | 5 | func configure(_ app: Application) throws { 6 | 7 | 8 | guard 9 | let jwksString = Environment.process.JWKS 10 | else { 11 | fatalError("No value was found at the given public key environment 'JWKS'") 12 | } 13 | 14 | 15 | guard 16 | let urlString = Environment.process.MYSQL_CRED 17 | else { 18 | fatalError("No value was found at the given public key environment 'MYSQL_CRED'") 19 | } 20 | 21 | guard 22 | let url = URL(string: urlString) 23 | else { 24 | fatalError("Cannot parse: \(urlString) correctly.") 25 | } 26 | 27 | app.databases.use(try DatabaseDriverFactory.mysql(url: url), as: DatabaseID.mysql) 28 | app.middleware.use(CORSMiddleware()) 29 | // app.middleware.use(ErrorMiddleware(()) 30 | 31 | try app.jwt.signers.use(jwksJSON: jwksString) 32 | app.server.configuration.supportCompression = true 33 | 34 | app.migrations.add(CreateOrder()) 35 | app.migrations.add(CreateOrderItem()) 36 | app.migrations.add(CreateOrderPayment()) 37 | 38 | try routes(app) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Chapter 3/FirstApp/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | func testCreateTodo() throws { 7 | let app = Application(.testing) 8 | defer { app.shutdown() } 9 | try configure(app) 10 | 11 | // replace default database with in-memory db for testing 12 | app.databases.use(.sqlite(.memory), as: .test, isDefault: true) 13 | // run migrations automatically 14 | try app.autoMigrate().wait() 15 | 16 | try app.test(.GET, "todos") { res in 17 | XCTAssertContent([Todo].self, res) { 18 | XCTAssertEqual($0.count, 0) 19 | } 20 | }.test(.POST, "todos", json: Todo(title: "Test My App")) { res in 21 | XCTAssertContent(Todo.self, res) { 22 | XCTAssertNotNil($0.id) 23 | XCTAssertEqual($0.title, "Test My App") 24 | } 25 | }.test(.GET, "todos") { res in 26 | XCTAssertContent([Todo].self, res) { 27 | XCTAssertEqual($0.count, 1) 28 | } 29 | } 30 | } 31 | } 32 | 33 | extension DatabaseID { 34 | static var test: Self { 35 | .init(string: "test") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter 3/FirstAppExtended/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import Fluent 3 | import XCTVapor 4 | 5 | final class AppTests: XCTestCase { 6 | func testCreateTodo() throws { 7 | let app = Application(.testing) 8 | defer { app.shutdown() } 9 | try configure(app) 10 | 11 | // replace default database with in-memory db for testing 12 | app.databases.use(.sqlite(.memory), as: .test, isDefault: true) 13 | // run migrations automatically 14 | try app.autoMigrate().wait() 15 | 16 | try app.test(.GET, "todos") { res in 17 | XCTAssertContent([Todo].self, res) { 18 | XCTAssertEqual($0.count, 0) 19 | } 20 | }.test(.POST, "todos", json: Todo(title: "Test My App")) { res in 21 | XCTAssertContent(Todo.self, res) { 22 | XCTAssertNotNil($0.id) 23 | XCTAssertEqual($0.title, "Test My App") 24 | } 25 | }.test(.GET, "todos") { res in 26 | XCTAssertContent([Todo].self, res) { 27 | XCTAssertEqual($0.count, 1) 28 | } 29 | } 30 | } 31 | } 32 | 33 | extension DatabaseID { 34 | static var test: Self { 35 | .init(string: "test") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "template", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"), 14 | .package(url: "https://github.com/proggeramlug/SimpleJWTMiddleware.git", from: "1.1.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: [ 18 | .product(name: "Fluent", package: "fluent"), 19 | .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver"), 20 | .product(name: "Vapor", package: "vapor"), 21 | .product(name: "SimpleJWTMiddleware", package: "SimpleJWTMiddleware") 22 | ]), 23 | .target(name: "Run", dependencies: ["App"]), 24 | .testTarget(name: "AppTests", dependencies: [ 25 | .target(name: "App"), 26 | .product(name: "XCTVapor", package: "vapor"), 27 | ]) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "ProductService", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"), 14 | .package(url: "https://github.com/proggeramlug/SimpleJWTMiddleware.git", from: "1.1.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: [ 18 | .product(name: "Fluent", package: "fluent"), 19 | .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver"), 20 | .product(name: "Vapor", package: "vapor"), 21 | .product(name: "SimpleJWTMiddleware", package: "SimpleJWTMiddleware") 22 | ]), 23 | .target(name: "Run", dependencies: ["App"]), 24 | .testTarget(name: "AppTests", dependencies: [ 25 | .target(name: "App"), 26 | .product(name: "XCTVapor", package: "vapor"), 27 | ]) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "ProductService", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), 14 | .package(url: "https://github.com/proggeramlug/SimpleJWTMiddleware.git", from: "1.1.0") 15 | ], 16 | targets: [ 17 | .target(name: "App", dependencies: [ 18 | .product(name: "Fluent", package: "fluent"), 19 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 20 | .product(name: "Vapor", package: "vapor"), 21 | .product(name: "SimpleJWTMiddleware", package: "SimpleJWTMiddleware") 22 | ]), 23 | .target(name: "Run", dependencies: ["App"]), 24 | .testTarget(name: "AppTests", dependencies: [ 25 | .target(name: "App"), 26 | .product(name: "XCTVapor", package: "vapor"), 27 | ]) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentMySQLDriver 3 | import Vapor 4 | import JWT 5 | 6 | // configures your application 7 | public func configure(_ app: Application) throws { 8 | // uncomment to serve files from /Public folder 9 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 10 | 11 | guard let jwksString = Environment.process.JWKS else { fatalError("No value was found at the given public key environment 'JWKS'") 12 | } 13 | 14 | guard let mysqlUrl = Environment.process.MYSQL_CRED else { fatalError("No value was found at the given public key environment 'MYSQL_CRED'") 15 | } 16 | guard let url = URL(string: mysqlUrl) else { fatalError("Cannot parse: \(mysqlUrl) correctly.") 17 | } 18 | app.databases.use(try .mysql(url: url), as: .mysql) 19 | 20 | app.middleware.use(CORSMiddleware()) 21 | app.middleware.use(ErrorMiddleware() { request, error in 22 | // TODO: Make this much nicer. 23 | return Response(status: .internalServerError) 24 | }) 25 | 26 | try app.jwt.signers.use(jwksJSON: jwksString) 27 | 28 | app.migrations.add(CreateCategory()) 29 | app.migrations.add(CreateProduct()) 30 | 31 | // register routes 32 | try routes(app) 33 | } 34 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import FluentSQLite 2 | import Vapor 3 | 4 | /// Called before your application initializes. 5 | public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { 6 | // Register providers first 7 | try services.register(FluentSQLiteProvider()) 8 | 9 | // Register routes to the router 10 | let router = EngineRouter.default() 11 | try routes(router) 12 | services.register(router, as: Router.self) 13 | 14 | // Register middleware 15 | var middlewares = MiddlewareConfig() // Create _empty_ middleware config 16 | // middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory 17 | middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response 18 | services.register(middlewares) 19 | 20 | // Configure a SQLite database 21 | let sqlite = try SQLiteDatabase(storage: .memory) 22 | 23 | // Register the configured SQLite database to the database config. 24 | var databases = DatabasesConfig() 25 | databases.add(database: sqlite, as: .sqlite) 26 | services.register(databases) 27 | 28 | // Configure migrations 29 | var migrations = MigrationConfig() 30 | migrations.add(model: Todo.self, database: .sqlite) 31 | services.register(migrations) 32 | } 33 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/boot.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import NIO 3 | 4 | private var verifyOrdersTask: RepeatedTask? = nil 5 | 6 | public func boot(_ app: Application) throws { 7 | 8 | let url = Environment.get("PRODUCT_SERVICE_URL") ?? "http://localhost:8080/v1/products?ids=" 9 | 10 | let processingController = ProcessingController(url) 11 | 12 | verifyOrdersTask = app.eventLoopGroup.next().scheduleRepeatedAsyncTask( 13 | initialDelay: .seconds(0), 14 | delay: .seconds(60) 15 | ) { [weak app] task -> EventLoopFuture in 16 | let app = app! 17 | 18 | var returnFuture:EventLoopFuture 19 | 20 | do { 21 | returnFuture = try processingController.getOrders(status: 0, on: app).flatMap { orders -> EventLoopFuture in 22 | var processingFutures:[EventLoopFuture] = [] 23 | 24 | for order in orders { 25 | processingFutures.append(processingController.processOrderInformation(order, on: app).transform(to: ())) 26 | } 27 | return processingFutures.flatten(on: app.eventLoopGroup.next()) 28 | } 29 | } 30 | catch let error { 31 | returnFuture = app.eventLoopGroup.next().makeFailedFuture(error) 32 | } 33 | 34 | return returnFuture 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "UserService", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), 14 | .package(url: "https://github.com/proggeramlug/SimpleJWTMiddleware.git", from: "1.1.0"), 15 | .package(url: "https://github.com/vapor-community/sendgrid.git", from: "4.0.0") 16 | ], 17 | targets: [ 18 | .target(name: "App", dependencies: [ 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | .product(name: "Vapor", package: "vapor"), 22 | .product(name: "SimpleJWTMiddleware", package: "SimpleJWTMiddleware"), 23 | .product(name: "SendGrid", package: "sendgrid") 24 | ]), 25 | .target(name: "Run", dependencies: ["App"]), 26 | .testTarget(name: "AppTests", dependencies: [ 27 | .target(name: "App"), 28 | .product(name: "XCTVapor", package: "vapor"), 29 | ]) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "UserService", 6 | platforms: [ 7 | .macOS(.v10_15) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"), 14 | .package(url: "https://github.com/proggeramlug/SimpleJWTMiddleware.git", from: "1.1.0"), 15 | .package(url: "https://github.com/vapor-community/sendgrid.git", from: "4.0.0") 16 | 17 | ], 18 | targets: [ 19 | .target(name: "App", dependencies: [ 20 | .product(name: "Fluent", package: "fluent"), 21 | .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver"), 22 | .product(name: "Vapor", package: "vapor"), 23 | .product(name: "SimpleJWTMiddleware", package: "SimpleJWTMiddleware"), 24 | .product(name: "SendGrid", package: "sendgrid") 25 | ]), 26 | .target(name: "Run", dependencies: ["App"]), 27 | .testTarget(name: "AppTests", dependencies: [ 28 | .target(name: "App"), 29 | .product(name: "XCTVapor", package: "vapor"), 30 | ]) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Chapter 3/Standard Template/web.Dockerfile: -------------------------------------------------------------------------------- 1 | # You can set the Swift version to what you need for your app. Versions can be found here: https://hub.docker.com/_/swift 2 | FROM swift:5.1.1 as builder 3 | 4 | # For local build, add `--build-arg env=docker` 5 | # In your application, you can use `Environment.custom(name: "docker")` to check if you're in this env 6 | ARG env 7 | 8 | RUN apt-get -qq update && apt-get install -y \ 9 | libssl-dev zlib1g-dev \ 10 | && rm -r /var/lib/apt/lists/* 11 | WORKDIR /app 12 | COPY . . 13 | RUN mkdir -p /build/lib && cp -R /usr/lib/swift/linux/*.so* /build/lib 14 | RUN swift build -c release && mv `swift build -c release --show-bin-path` /build/bin 15 | 16 | # Production image 17 | FROM ubuntu:18.04 18 | ARG env 19 | # DEBIAN_FRONTEND=noninteractive for automatic UTC configuration in tzdata 20 | RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 21 | libatomic1 libicu60 libxml2 libcurl4 libz-dev libbsd0 tzdata \ 22 | && rm -r /var/lib/apt/lists/* 23 | WORKDIR /app 24 | COPY --from=builder /build/bin/Run . 25 | COPY --from=builder /build/lib/* /usr/lib/ 26 | # Uncomment the next line if you need to load resources from the `Public` directory 27 | #COPY --from=builder /app/Public ./Public 28 | # Uncomment the next line if you are using Leaf 29 | #COPY --from=builder /app/Resources ./Resources 30 | ENV ENVIRONMENT=$env 31 | 32 | ENTRYPOINT ./Run serve --env $ENVIRONMENT --hostname 0.0.0.0 --port 80 33 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/JWTMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import JWT 3 | 4 | final class JWTMiddleware: Middleware { 5 | init() { } 6 | 7 | func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { 8 | 9 | guard let token = request.headers.bearerAuthorization?.token.utf8 else { 10 | return request.eventLoop.makeFailedFuture(Abort(.unauthorized, reason: "Missing authorization bearer header")) 11 | } 12 | 13 | do { 14 | request.payload = try request.jwt.verify(Array(token), as: Payload.self) 15 | } catch let JWTError.claimVerificationFailure(name: name, reason: reason) { 16 | request.logger.error("JWT Verification Failure: \(name), \(reason)") 17 | return request.eventLoop.makeFailedFuture(JWTError.claimVerificationFailure(name: name, reason: reason)) 18 | } catch let error { 19 | return request.eventLoop.makeFailedFuture(error) 20 | } 21 | 22 | return next.respond(to: request) 23 | } 24 | 25 | } 26 | 27 | extension AnyHashable { 28 | static let payload: String = "jwt_payload" 29 | } 30 | 31 | extension Request { 32 | var loggedIn: Bool { 33 | if (self.userInfo[.payload] as? Payload) != nil { 34 | return true 35 | } 36 | return false 37 | } 38 | var payload: Payload { 39 | get { self.userInfo[.payload] as! Payload } 40 | set { self.userInfo[.payload] = newValue } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Models/Database/Order.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Order: Model, Content { 5 | static let schema = "orders" 6 | 7 | @ID(key: "id") 8 | var id: Int? 9 | 10 | @Field(key: "totalAmount") 11 | var totalAmount: Int 12 | 13 | @Field(key: "paidAmount") 14 | var paidAmount: Int 15 | 16 | @Field(key: "userId") 17 | var userId: Int 18 | 19 | @Field(key: "status") 20 | var status: Int 21 | 22 | @Timestamp(key: "createdAt", on: .create) 23 | var createdAt: Date? 24 | 25 | @Timestamp(key: "updatedAt", on: .update) 26 | var updatedAt: Date? 27 | 28 | @Timestamp(key: "deletedAt", on: .delete) 29 | var deletedAt: Date? 30 | 31 | @Field(key: "firstname") 32 | var firstname: String 33 | 34 | @Field(key: "lastname") 35 | var lastname: String 36 | 37 | @Field(key: "street") 38 | var street: String 39 | 40 | @Field(key: "zip") 41 | var zip: String 42 | 43 | @Field(key: "city") 44 | var city: String 45 | 46 | @Children(for: \OrderItem.$order) 47 | var items: [OrderItem] 48 | 49 | init() { } 50 | 51 | init( 52 | id: Int? = nil, 53 | totalAmount: Int, 54 | paidAmount: Int = 0, 55 | status: Int = 0, 56 | firstname: String, 57 | lastname: String, 58 | street: String, 59 | zip: String, 60 | city: String) { 61 | self.id = id 62 | self.totalAmount = totalAmount 63 | self.firstname = firstname 64 | self.lastname = lastname 65 | self.street = street 66 | self.paidAmount = paidAmount 67 | self.status = status 68 | self.zip = zip 69 | self.city = city 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | import JWT 5 | 6 | // configures your application 7 | public func configure(_ app: Application) throws { 8 | // uncomment to serve files from /Public folder 9 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 10 | 11 | guard let jwksString = Environment.process.JWKS else { 12 | fatalError("No value was found at the given public key environment 'JWKS'") 13 | } 14 | 15 | guard let psqlUrl = Environment.process.PSQL_CRED else { 16 | fatalError("No value was found at the given public key environment 'PSQL_CRED'") 17 | } 18 | 19 | guard let url = URL(string: psqlUrl) else { 20 | fatalError("Cannot parse: \(psqlUrl) correctly.") 21 | } 22 | 23 | app.databases.use(try .postgres(url: url), as: .psql) 24 | app.middleware.use(CORSMiddleware()) 25 | app.middleware.use(ErrorMiddleware() { request, error in 26 | struct ErrorResponse: Content { 27 | var error: String 28 | } 29 | let data: Data? 30 | do { 31 | data = try JSONEncoder().encode(ErrorResponse(error: "\(error)")) 32 | } 33 | catch { 34 | data = "{\"error\":\"\(error)\"}".data(using: .utf8) 35 | } 36 | let res = Response(status: .internalServerError, body: Response.Body(data: data!)) 37 | res.headers.replaceOrAdd(name: .contentType, value: "application/json") 38 | return res 39 | }) 40 | 41 | try app.jwt.signers.use(jwksJSON: jwksString) 42 | 43 | app.migrations.add(CreateCategory()) 44 | app.migrations.add(CreateProduct()) 45 | 46 | // register routes 47 | try routes(app) 48 | } 49 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentMySQLDriver 3 | import Vapor 4 | import JWT 5 | import SendGrid 6 | 7 | // configures your application 8 | public func configure(_ app: Application) throws { 9 | // uncomment to serve files from /Public folder 10 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 11 | 12 | guard let jwksString = Environment.process.JWKS else { fatalError("No value was found at the given public key environment 'JWKS'") 13 | } 14 | 15 | guard let mysqlUrl = Environment.process.MYSQL_CRED else { fatalError("No value was found at the given public key environment 'MYSQL_CRED'") 16 | } 17 | guard let url = URL(string: mysqlUrl) else { fatalError("Cannot parse: \(mysqlUrl) correctly.") 18 | } 19 | 20 | app.databases.use(try .mysql(url: url), as: .mysql) 21 | app.middleware.use(CORSMiddleware()) 22 | app.middleware.use(ErrorMiddleware() { request, error in 23 | struct ErrorResponse: Content { 24 | var error: String 25 | } 26 | let data: Data? 27 | do { 28 | data = try JSONEncoder().encode(ErrorResponse(error: "\(error)")) 29 | } 30 | catch { 31 | data = "{\"error\":\"\(error)\"}".data(using: .utf8) 32 | } 33 | let res = Response(status: .internalServerError, body: Response.Body(data: data!)) 34 | res.headers.replaceOrAdd(name: .contentType, value: "application/json") 35 | return res 36 | }) 37 | 38 | try app.jwt.signers.use(jwksJSON: jwksString) 39 | 40 | app.sendgrid.initialize() 41 | 42 | app.migrations.add(CreateUser()) 43 | app.migrations.add(CreateAddress()) 44 | 45 | // register routes 46 | try routes(app) 47 | } 48 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | import JWT 5 | import SendGrid 6 | 7 | // configures your application 8 | public func configure(_ app: Application) throws { 9 | // uncomment to serve files from /Public folder 10 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 11 | 12 | guard let jwksString = Environment.process.JWKS else { 13 | fatalError("No value was found at the given public key environment 'JWKS'") 14 | } 15 | 16 | guard let psqlUrl = Environment.process.PSQL_CRED else { 17 | fatalError("No value was found at the given public key environment 'PSQL_CRED'") 18 | } 19 | 20 | guard let url = URL(string: psqlUrl) else { 21 | fatalError("Cannot parse: \(psqlUrl) correctly.") 22 | } 23 | app.databases.use(try .postgres(url: url), as: .psql) 24 | app.middleware.use(CORSMiddleware()) 25 | app.middleware.use(ErrorMiddleware() { request, error in 26 | struct ErrorResponse: Content { 27 | var error: String 28 | } 29 | let data: Data? 30 | do { 31 | data = try JSONEncoder().encode(ErrorResponse(error: "\(error)")) 32 | } 33 | catch { 34 | data = "{\"error\":\"\(error)\"}".data(using: .utf8) 35 | } 36 | let res = Response(status: .internalServerError, body: Response.Body(data: data!)) 37 | res.headers.replaceOrAdd(name: .contentType, value: "application/json") 38 | return res 39 | }) 40 | 41 | try app.jwt.signers.use(jwksJSON: jwksString) 42 | 43 | app.sendgrid.initialize() 44 | 45 | app.migrations.add(CreateUser()) 46 | app.migrations.add(CreateAddress()) 47 | 48 | // register routes 49 | try routes(app) 50 | } 51 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Controllers/ProcessingController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class ProcessingController { 5 | let productServiceUrl:String 6 | 7 | init(_ productServiceUrl: String) { 8 | self.productServiceUrl = productServiceUrl 9 | } 10 | 11 | func getOrders(status: Int = 0, on app: Application) throws -> EventLoopFuture<[Order]> { 12 | return Order.query(on: app.db).filter(\Order.$status == status).all() 13 | } 14 | 15 | func processOrderInformation(_ order: Order, on app: Application) -> EventLoopFuture { 16 | let productIds:[Int] = order.items.map { $0.productId } 17 | 18 | let expectedTotal = order.totalAmount 19 | 20 | return self.getProductInformation(productIds: productIds, client: app.client).flatMap { (products:[Product]) -> EventLoopFuture in 21 | var total = 0 22 | for item in order.items { 23 | for product in products { 24 | if product.id == item.productId { 25 | total = total + product.unitPrice * item.quantity 26 | } 27 | } 28 | } 29 | if total != expectedTotal { 30 | return app.eventLoopGroup.next().makeFailedFuture(OrderError.totalsNotMatching) 31 | } 32 | order.totalAmount = total 33 | order.status = 1 34 | return order.save(on: app.db).transform(to: true) 35 | } 36 | } 37 | 38 | 39 | func getProductInformation(productIds: [Int], client: Client) -> EventLoopFuture<[Product]> { 40 | return client.get(URI(string: self.productServiceUrl+productIds.map { String($0) }.joined(separator: ",")), headers: ["Content-Type": "application/json"]).flatMapThrowing { (response:ClientResponse) in 41 | return try response.content.decode([Product].self) 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose up migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | DATABASE_HOST: db 25 | DATABASE_NAME: vapor_database 26 | DATABASE_USERNAME: vapor_username 27 | DATABASE_PASSWORD: vapor_password 28 | 29 | services: 30 | app: 31 | image: template:latest 32 | build: 33 | context: . 34 | environment: 35 | <<: *shared_environment 36 | depends_on: 37 | - db 38 | ports: 39 | - '8080:80' 40 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 41 | migrate: 42 | image: template:latest 43 | build: 44 | context: . 45 | environment: 46 | <<: *shared_environment 47 | depends_on: 48 | - db 49 | command: ["migrate", "--yes"] 50 | deploy: 51 | replicas: 0 52 | revert: 53 | image: template:latest 54 | build: 55 | context: . 56 | environment: 57 | <<: *shared_environment 58 | depends_on: 59 | - db 60 | command: ["migrate", "--revert", "--yes"] 61 | db: 62 | image: mysql:8.0 63 | volumes: 64 | - db_data:/var/lib/mysql 65 | environment: 66 | MYSQL_USER: vapor_username 67 | MYSQL_PASSWORD: vapor_password 68 | MYSQL_DATABASE: vapor_database 69 | MYSQL_RANDOM_ROOT_PASSWORD: 'yes' 70 | ports: 71 | - '3306:3306' -------------------------------------------------------------------------------- /Chapter 6/Shop Backend/template/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose up migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | DATABASE_HOST: db 25 | DATABASE_NAME: vapor_database 26 | DATABASE_USERNAME: vapor_username 27 | DATABASE_PASSWORD: vapor_password 28 | 29 | services: 30 | app: 31 | image: template:latest 32 | build: 33 | context: . 34 | environment: 35 | <<: *shared_environment 36 | depends_on: 37 | - db 38 | ports: 39 | - '8080:80' 40 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 41 | migrate: 42 | image: template:latest 43 | build: 44 | context: . 45 | environment: 46 | <<: *shared_environment 47 | depends_on: 48 | - db 49 | command: ["migrate", "--yes"] 50 | deploy: 51 | replicas: 0 52 | revert: 53 | image: template:latest 54 | build: 55 | context: . 56 | environment: 57 | <<: *shared_environment 58 | depends_on: 59 | - db 60 | command: ["migrate", "--revert", "--yes"] 61 | db: 62 | image: mysql:8.0 63 | volumes: 64 | - db_data:/var/lib/mysql 65 | environment: 66 | MYSQL_USER: vapor_username 67 | MYSQL_PASSWORD: vapor_password 68 | MYSQL_DATABASE: vapor_database 69 | MYSQL_RANDOM_ROOT_PASSWORD: 'yes' 70 | ports: 71 | - '3306:3306' -------------------------------------------------------------------------------- /Chapter 7/UserService/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose up migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | MYSQL_CRED: mysql://vapor_username:vapor_password@db:3306/vapor_database?ssl=false 25 | JWKS: '{"keys":[{"kty":"RSA","d":"<< insert d >>","e":"AQAB","use":"sig","kid":"backend","alg":"RS256","n":"<< insert n >>"}]}' 26 | SENDGRID_API_KEY: asdf 27 | 28 | services: 29 | app: 30 | image: userservice:latest 31 | build: 32 | context: . 33 | environment: 34 | <<: *shared_environment 35 | depends_on: 36 | - db 37 | ports: 38 | - '8080:80' 39 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 40 | migrate: 41 | image: userservice:latest 42 | build: 43 | context: . 44 | environment: 45 | <<: *shared_environment 46 | depends_on: 47 | - db 48 | command: ["migrate", "--yes"] 49 | deploy: 50 | replicas: 0 51 | revert: 52 | image: userservice:latest 53 | build: 54 | context: . 55 | environment: 56 | <<: *shared_environment 57 | depends_on: 58 | - db 59 | command: ["migrate", "--revert", "--yes"] 60 | db: 61 | image: mysql:8.0 62 | volumes: 63 | - db_data:/var/lib/mysql 64 | environment: 65 | MYSQL_USER: vapor_username 66 | MYSQL_PASSWORD: vapor_password 67 | MYSQL_DATABASE: vapor_database 68 | MYSQL_RANDOM_ROOT_PASSWORD: 'yes' 69 | command: ["--default-authentication-plugin=mysql_native_password"] 70 | ports: 71 | - '3306:3306' 72 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Controllers/CategoriesController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class CategoriesController { 5 | 6 | func get(_ request: Request)throws -> EventLoopFuture<[CategoryResponse]> { 7 | return Category.query(on: request.db).all().map { cats in 8 | return cats.map { CategoryResponse(id: $0.id!, name: $0.name) } 9 | } 10 | } 11 | 12 | func new(_ request: Request)throws -> EventLoopFuture { 13 | let input = try request.content.decode(CategoryInput.self) 14 | let category = Category(name: input.name) 15 | return category.save(on: request.db).map { _ in 16 | return CategoryResponse(id: category.id!, name: category.name) 17 | } 18 | } 19 | 20 | func edit(_ request: Request)throws -> EventLoopFuture { 21 | if let id = Int(request.parameters.get("id") ?? "0") 22 | { 23 | let input = try request.content.decode(CategoryInput.self) 24 | 25 | return Category.query(on: request.db).filter(\.$id == id).all().flatMap { categories in 26 | if categories.count == 0 { 27 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No product. found!")) 28 | } 29 | let category = categories.first! 30 | 31 | category.name = input.name 32 | 33 | return category.save(on: request.db).map { _ in 34 | return CategoryResponse(id: category.id!, name: category.name) 35 | } 36 | } 37 | } 38 | else { 39 | throw Abort(.badRequest, reason: "No input given.") 40 | } 41 | } 42 | 43 | func delete(_ request: Request)throws -> EventLoopFuture { 44 | if let id = Int(request.parameters.get("id") ?? "0") { 45 | return Category.query(on: request.db).filter(\.$id == id).delete().map { _ in 46 | return .ok 47 | } 48 | } 49 | else { 50 | throw Abort(.badRequest, reason: "No id given.") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Controllers/CategoriesController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class CategoriesController { 5 | 6 | func get(_ request: Request)throws -> EventLoopFuture<[CategoryResponse]> { 7 | return Category.query(on: request.db).all().map { cats in 8 | return cats.map { CategoryResponse(id: $0.id!, name: $0.name) } 9 | } 10 | } 11 | 12 | func new(_ request: Request)throws -> EventLoopFuture { 13 | let input = try request.content.decode(CategoryInput.self) 14 | let category = Category(name: input.name) 15 | return category.save(on: request.db).map { _ in 16 | return CategoryResponse(id: category.id!, name: category.name) 17 | } 18 | } 19 | 20 | func edit(_ request: Request)throws -> EventLoopFuture { 21 | if let id = Int(request.parameters.get("id") ?? "0") 22 | { 23 | let input = try request.content.decode(CategoryInput.self) 24 | 25 | return Category.query(on: request.db).filter(\.$id == id).all().flatMap { categories in 26 | if categories.count == 0 { 27 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No product. found!")) 28 | } 29 | let category = categories.first! 30 | 31 | category.name = input.name 32 | 33 | return category.save(on: request.db).map { _ in 34 | return CategoryResponse(id: category.id!, name: category.name) 35 | } 36 | } 37 | } 38 | else { 39 | throw Abort(.badRequest, reason: "No input given.") 40 | } 41 | } 42 | 43 | func delete(_ request: Request)throws -> EventLoopFuture { 44 | if let id = Int(request.parameters.get("id") ?? "0") { 45 | return Category.query(on: request.db).filter(\.$id == id).delete().map { _ in 46 | return .ok 47 | } 48 | } 49 | else { 50 | throw Abort(.badRequest, reason: "No id given.") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class UserController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) { 7 | routes.get("profile", use: profile) 8 | routes.post("profile", use: save) 9 | routes.get("status", use: status) 10 | routes.delete("user", use: delete) 11 | } 12 | 13 | func profile(_ request: Request) throws -> EventLoopFuture { 14 | return User.query(on: request.db).filter(\.$id == request.payload.id).all().map { users in 15 | return UserSuccessResponse(user: UserResponse(user: users.first!)) 16 | } 17 | } 18 | 19 | func status(_ request: Request) throws -> EventLoopFuture { 20 | if request.loggedIn == false { 21 | throw Abort(.unauthorized) 22 | } 23 | return User.query(on: request.db).filter(\.$id == request.payload.id).all().map { users in 24 | return UserSuccessResponse(user: UserResponse(user: users.first!)) 25 | } 26 | } 27 | 28 | func save(_ request: Request) throws -> EventLoopFuture { 29 | 30 | let content = try request.content.decode(EditUserInput.self) 31 | return User.query(on: request.db).filter(\.$id == request.payload.id).first().flatMap { user in 32 | let user = user! 33 | if let name = content.firstname { 34 | user.firstname = name 35 | } 36 | if let name = content.lastname { 37 | user.lastname = name 38 | } 39 | 40 | return user.update(on: request.db).map { _ in 41 | return UserSuccessResponse(user: UserResponse(user: user)) 42 | } 43 | } 44 | 45 | 46 | } 47 | 48 | func delete(_ request: Request)throws -> EventLoopFuture { 49 | 50 | return User.query(on: request.db).filter(\.$id == request.payload.id).first().flatMap { user in 51 | let user = user! 52 | return Address.query(on: request.db).filter(\.$userId == user.id!).delete().flatMap { 53 | return user.delete(on: request.db).transform(to: .ok) 54 | } 55 | } 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Chapter 11/OrderService/Sources/App/Controllers/OrderController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class OrderController { 5 | func list(req: Request) throws -> EventLoopFuture<[OrderResponse]> { 6 | return Order.query(on: req.db).all().map { orders in 7 | return orders.map { return OrderResponse(order: $0, items: $0.items) } 8 | } 9 | 10 | } 11 | 12 | func listMine(req: Request) throws -> EventLoopFuture<[OrderResponse]> { 13 | return Order.query(on: req.db).all().map { orders in 14 | return orders.map { return OrderResponse(order: $0, items: $0.items) } 15 | } 16 | } 17 | 18 | func postPayment(req: Request) throws -> EventLoopFuture { 19 | let paymentInput = try req.content.decode(PaymentInput.self) 20 | 21 | return Order.query(on: req.db).filter(\Order.$id == paymentInput.orderId).all().flatMap { orders in 22 | if orders.count == 0 { 23 | return req.eventLoop.makeFailedFuture(OrderError.orderNotFound) 24 | } 25 | 26 | let payment = OrderPayment(id: nil, totalAmount: paymentInput.totalAmount, method: paymentInput.method, order: orders.first!) 27 | 28 | return payment.save(on: req.db).transform(to: AddedPaymentResponse()) 29 | } 30 | } 31 | 32 | func post(req: Request) throws -> EventLoopFuture { 33 | let orderInput = try req.content.decode(OrderInput.self) 34 | 35 | let order = Order(totalAmount: orderInput.totalAmount, firstname: orderInput.firstname, lastname: orderInput.lastname, street: orderInput.street, zip: orderInput.zip, city: orderInput.city) 36 | 37 | return order.save(on: req.db).flatMap { 38 | var saving:[EventLoopFuture] = [] 39 | var items:[OrderItem] = [] 40 | 41 | for inputItem in orderInput.items { 42 | let item = OrderItem(totalAmount: inputItem.unitPrice*inputItem.quantity, unitPrice: inputItem.unitPrice, quantity: inputItem.quantity, order: order) 43 | saving.append(item.save(on: req.db).map { 44 | items.append(item) 45 | }) 46 | } 47 | 48 | return saving.flatten(on: req.eventLoop).map { 49 | return OrderResponse(order: order, items: items) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Controllers/AddressController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class AddressController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) { 7 | routes.get("", use: addresses) 8 | routes.post("", use: self.create) 9 | routes.patch(":id", use: self.update) 10 | routes.delete(":id", use: delete) 11 | } 12 | 13 | func addresses(_ request: Request)throws -> EventLoopFuture<[AddressResponse]> { 14 | return Address.query(on: request.db).filter(\.$userId == request.payload.id).all().map { addresses in 15 | return addresses.map { AddressResponse($0) } 16 | } 17 | } 18 | 19 | func create(_ request: Request)throws -> EventLoopFuture { 20 | let content = try request.content.decode(AddressInput.self) 21 | 22 | let address = Address(street: content.street, city: content.city, zip: content.zip, userId: request.payload.id) 23 | 24 | return address.save(on: request.db).map { _ in 25 | return AddressResponse(address) 26 | } 27 | } 28 | 29 | func update(_ request: Request)throws -> EventLoopFuture { 30 | 31 | let content = try request.content.decode(AddressInput.self) 32 | let id = Int(request.parameters.get("id") ?? "0") ?? 0 33 | 34 | return Address.query(on: request.db).filter(\.$id == id).filter(\.$userId == request.payload.id).all().flatMap { addresses in 35 | if addresses.count == 0 { 36 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No address found!")) 37 | } 38 | let address = addresses.first! 39 | address.street = content.street 40 | address.city = content.city 41 | address.zip = content.zip 42 | 43 | return address.save(on: request.db).map { _ in 44 | return AddressResponse(address) 45 | } 46 | } 47 | } 48 | 49 | func delete(_ request: Request)throws -> EventLoopFuture { 50 | let id = Int(request.parameters.get("id") ?? "0") ?? 0 51 | 52 | return Address.query(on: request.db).filter(\.$id == id).filter(\.$userId == request.payload.id).all().flatMap { addresses in 53 | if addresses.count == 0 { 54 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No address found!")) 55 | } 56 | return addresses.first!.delete(on: request.db).transform(to: .ok) 57 | } 58 | } 59 | } 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Chapter 7/UserService/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class UserController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) { 7 | routes.get("profile", use: profile) 8 | routes.post("profile", use: save) 9 | routes.get("status", use: status) 10 | routes.delete("user", use: delete) 11 | } 12 | 13 | func profile(_ request: Request) throws -> EventLoopFuture { 14 | return User.query(on: request.db).filter(\.$id == request.payload.id).all().map { users in 15 | return UserSuccessResponse(user: UserResponse(user: users.first!)) 16 | } 17 | } 18 | 19 | func status(_ request: Request) throws -> EventLoopFuture { 20 | if request.loggedIn == false { 21 | throw Abort(.unauthorized) 22 | } 23 | return User.query(on: request.db).filter(\.$id == request.payload.id).all().map { users in 24 | return UserSuccessResponse(user: UserResponse(user: users.first!)) 25 | } 26 | } 27 | 28 | func save(_ request: Request) throws -> EventLoopFuture { 29 | 30 | let content = try request.content.decode(EditUserInput.self) 31 | return User.query(on: request.db).filter(\.$id == request.payload.id).first().flatMap { user in 32 | let user = user! 33 | if let name = content.firstname { 34 | user.firstname = name 35 | } 36 | if let name = content.lastname { 37 | user.lastname = name 38 | } 39 | 40 | return user.update(on: request.db).map { _ in 41 | return UserSuccessResponse(user: UserResponse(user: user)) 42 | } 43 | } 44 | 45 | 46 | } 47 | 48 | func delete(_ request: Request)throws -> EventLoopFuture { 49 | 50 | return User.query(on: request.db).filter(\.$id == request.payload.id).first().flatMap { user in 51 | // Because the JWT is not immediately invalid the user could send the same request twice, resulting in an error here if we assume the user exists. 52 | if let user = user { 53 | return Address.query(on: request.db).filter(\.$userId == user.id!).delete().flatMap { 54 | return user.delete(on: request.db).transform(to: .ok) 55 | } 56 | } 57 | else { 58 | return request.eventLoop.makeSucceededFuture(.conflict) 59 | } 60 | } 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/Sources/App/Controllers/AddressController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class AddressController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) { 7 | routes.get("", use: addresses) 8 | routes.post("", use: self.create) 9 | routes.patch(":id", use: self.update) 10 | routes.delete(":id", use: delete) 11 | } 12 | 13 | func addresses(_ request: Request)throws -> EventLoopFuture<[AddressResponse]> { 14 | return Address.query(on: request.db).filter(\.$userId == request.payload.id).all().map { addresses in 15 | return addresses.map { AddressResponse($0) } 16 | } 17 | } 18 | 19 | func create(_ request: Request)throws -> EventLoopFuture { 20 | let content = try request.content.decode(AddressInput.self) 21 | 22 | let address = Address(street: content.street, city: content.city, zip: content.zip, userId: request.payload.id) 23 | 24 | return address.save(on: request.db).map { _ in 25 | return AddressResponse(address) 26 | } 27 | } 28 | 29 | func update(_ request: Request)throws -> EventLoopFuture { 30 | 31 | let content = try request.content.decode(AddressInput.self) 32 | let id = Int(request.parameters.get("id") ?? "0") ?? 0 33 | 34 | return Address.query(on: request.db).filter(\.$id == id).filter(\.$userId == request.payload.id).all().flatMap { addresses in 35 | if addresses.count == 0 { 36 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No address found!")) 37 | } 38 | let address = addresses.first! 39 | address.street = content.street 40 | address.city = content.city 41 | address.zip = content.zip 42 | 43 | return address.save(on: request.db).map { _ in 44 | return AddressResponse(address) 45 | } 46 | } 47 | } 48 | 49 | func delete(_ request: Request)throws -> EventLoopFuture { 50 | let id = Int(request.parameters.get("id") ?? "0") ?? 0 51 | 52 | return Address.query(on: request.db).filter(\.$id == id).filter(\.$userId == request.payload.id).all().flatMap { addresses in 53 | if addresses.count == 0 { 54 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No address found!")) 55 | } 56 | return addresses.first!.delete(on: request.db).transform(to: .ok) 57 | } 58 | } 59 | } 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Chapter 7/UserService-Postgres/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose up migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | PSQL_CRED: postgres://vapor_username:vapor_password@db:5432/vapor_database?ssl=false 25 | JWKS: '{"keys":[{"kty":"RSA","d":"BGSH12mVZbAlYny1C9KlswOORGsz0odL_NmvU-6mA_nNxXKz6nQIK6Il6L8VPYa9dZb1HwkFRDdyWqLYmjrTlQZievU9VLvmWaUlKXyIwOiXF3vqmzQy_luR-lE5v05pOqUgPhnPrQtGPvSuQpAYLFSJCX9YLJdmI0LA-0G_AvVnSjjSBnmvwZEKfHsZSRRC_cDOk8pm4A3iEMQv1_3xdwZ5GeKtkuAdNi9T31lMNlnKj-A5hZq6rfwiAWdxvCPqg44EyCKhj6c6sm2aM0qqKYb3sYMkYRmZh4GzaX8yQB3roQG-FLZ18VRm9q9YPuFavOL_L_NC8JG2Z6use4DbsQ","e":"AQAB","use":"sig","kid":"backend","alg":"RS256","n":"ghZ4Eb-LT2tcCfWI6Szxu25kLQ3LOADA-KydgxASz_jMb5IHxMQIwMKFXe5qQ8lIG1lFhdu2G51GdXA8vaY148UtYt_COUnQSYOfIhcp7WpPXXhu83vqjET78CzkLsDI8VmF-9d8dHvtSVUqXfkk83kLOYmBcEamMWICUJj1yTiipqknuDZSMfIccXdhWEXr8gpl1cVZ5G2QZVNpFl1wGJ2UvwbQx6t9M6LDD9c9pqKc2-1X7pNLb-UekxhYzJHeRko288REN8AR7czaMQtZrB2hTEJAVixUu6KPGfSHxp49K2Hy2a1UC0nlohcrJ0ERfgMJ6oZ_n_kYeLx6nHeimw"}]}' 26 | SENDGRID_API_KEY: asdf 27 | 28 | services: 29 | app: 30 | image: userservice:latest 31 | build: 32 | context: . 33 | environment: 34 | <<: *shared_environment 35 | depends_on: 36 | - db 37 | ports: 38 | - '8080:80' 39 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "80"] 40 | migrate: 41 | image: userservice:latest 42 | build: 43 | context: . 44 | environment: 45 | <<: *shared_environment 46 | depends_on: 47 | - db 48 | command: ["migrate", "--yes"] 49 | deploy: 50 | replicas: 0 51 | revert: 52 | image: userservice:latest 53 | build: 54 | context: . 55 | environment: 56 | <<: *shared_environment 57 | depends_on: 58 | - db 59 | command: ["migrate", "--revert", "--yes"] 60 | db: 61 | image: postgres:12.1-alpine 62 | volumes: 63 | - db_data:/var/lib/postgresql/data/pgdata 64 | environment: 65 | PGDATA: /var/lib/postgresql/data/pgdata 66 | POSTGRES_USER: vapor_username 67 | POSTGRES_PASSWORD: vapor_password 68 | POSTGRES_DB: vapor_database 69 | expose: 70 | - 5432 71 | ports: 72 | - '5432:5432' 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Hands-On-Microservices-with-Swift-5 5 | Hands-On Microservices with Swift 5, published by Packt! 6 | 7 | 8 | # Using docker-compose # 9 | Make sure you have the `docker-compose.yml` updated to reflect your environment variables. 10 | For you to run any service using the `docker-compose.yml` you need to run the following commands: 11 | ``` 12 | docker-compose up db 13 | ``` 14 | Then use another terminal (or shell/bash) window to run: 15 | ``` 16 | docker-compose up migrate 17 | docker-compose up app 18 | ``` 19 | This follows the standard Vapor docker-compose.yml and will get your app up and running. 20 | 21 | *Using MySQL without docker-compose:* Make sure you are passing this argument when starting: `--default-authentication-plugin=mysql_native_password` (this will be updated in the book as well). 22 | 23 | # Change Log # 24 | ## 09/01/20: Vapor 4 Update 25 | 26 | **All projects have been updated to the final Vapor 4 versions.** 27 | 28 | 29 | ## 05/01/20: Chapter 9 Update 30 | Various smaller issues have been corrected, among those: 31 | - Updates for the newer Vapor versions 32 | - Adding the test suite for Postman 33 | - Added a Postgres Version of the service. 34 | 35 | ## 05/01/20: Chapter 7 Update 36 | Various smaller issues have been corrected, most notably: 37 | - Addresses have now a sub-path "addresses" 38 | - Adjustments for new Vapor versions 39 | - Adding the test suite for Postman. 40 | 41 | ## 04/13/20: Chapter 7 Update 42 | Updated the dependency to Vapor 4.0.0. Also an update to the _SimpleJWTMiddleware_ has been performed in another repository. 43 | 44 | ## 04/13/20: Chapter 6 Update 45 | Updated the dependency to Vapor 4.0.0. Also an update to the _SimpleJWTMiddleware_ has been performed in another repository. 46 | 47 | _long COVID-19 related break_ 48 | ## 03/24/20: Chapter 7 Update ## 49 | The `docker-compose.yml` is now working as expected with MySQL8. Small fixes within the service: 50 | - A typo disabling `JWTMiddleware` from working in address routes. 51 | - More sophisticated error management. 52 | - Docker-compose update. 53 | - Sample .env file is now provided: `.env_sample` 54 | 55 | *PostgreSQL Version now available:* You can now also use PostgreSQL if you prefer that in chapter 7. Other chapters will follow. 56 | 57 | ## 03/15/20: Chapter 9 Update ## 58 | The product service in chapter 9 is now compatible with the latest Vapor RC. 59 | 60 | ## 03/15/20: Chapter 7 Update ## 61 | Minor bug fix since `CORSMiddleware` was initialized twice. 62 | 63 | ## 03/13/20: Chapter 7 Update ## 64 | Code is now working with the latest RC. While going through the code and validating I'm finding places that need improvement (beyond just the RC update). 65 | 66 | Do not hesitate to reach out if you have improvements that make sense to you. 67 | It used to be that `ErrorMiddleware` would return errors automatically and nicely formatted. That is something we need to do ourselves now, I left the code very generic for now but I'm working on coming up with something better. 68 | 69 | ## 03/08/20: Chapter 5 + 6 Update ## 70 | The template has been updated to work with the most recent version of Vapor and Fluent. 71 | ### Download a free PDF 72 | 73 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
74 |

https://packt.link/free-ebook/9781789530889

75 | -------------------------------------------------------------------------------- /Chapter 9/ProductService/Sources/App/Controllers/ProductController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentKit 3 | import Vapor 4 | 5 | final class ProductsController { 6 | 7 | func get(_ request: Request)throws -> EventLoopFuture<[ProductResponse]> { 8 | let querybuilder = Product.query(on: request.db) 9 | 10 | do { 11 | if let categoryId = try request.query.get(Int?.self, at: "categoryId") { 12 | querybuilder.filter(\.$categoryId == categoryId) 13 | } 14 | } 15 | catch {} 16 | 17 | do { 18 | if let query = try request.query.get(String?.self, at: "query") { 19 | querybuilder.filter(\.$name ~~ query) 20 | querybuilder.group(.or) { 21 | $0.filter(\Product.$name ~~ query).filter(\Product.$description ~~ query) 22 | } 23 | } 24 | } 25 | catch {} 26 | 27 | do { 28 | if let idsString = try request.query.get(String?.self, at: "ids") { 29 | let ids:[Int] = idsString.split(separator: ",").map { Int(String($0)) ?? 0 } 30 | querybuilder.filter(\.$id ~~ ids) 31 | } 32 | } 33 | catch {} 34 | 35 | return querybuilder.all().map { products in 36 | return products.map { ProductResponse(id: $0.id!, name: $0.name, description: $0.description, price: $0.price) } 37 | } 38 | } 39 | 40 | func new(_ request: Request)throws -> EventLoopFuture { 41 | let input = try request.content.decode(ProductInput.self) 42 | let product = Product(name: input.name, description: input.description, price: input.price, categoryId: input.categoryId) 43 | return product.save(on: request.db).map { _ in 44 | return ProductResponse(id: product.id!, name: product.name, description: product.description, price: product.price) 45 | } 46 | } 47 | 48 | func edit(_ request: Request)throws -> EventLoopFuture { 49 | let input = try request.content.decode(ProductInput.self) 50 | if let id = request.parameters.get("id", as: Int.self) 51 | { 52 | return Product.query(on: request.db).filter(\.$id == id).all().flatMap { products in 53 | if products.count == 0 { 54 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No product. found!")) 55 | } 56 | let product = products.first! 57 | 58 | product.name = input.name 59 | product.description = input.description 60 | product.price = input.price 61 | product.categoryId = input.categoryId 62 | 63 | return product.save(on: request.db).map { _ in 64 | return ProductResponse(id: product.id!, name: product.name, description: product.description, price: product.price) 65 | } 66 | } 67 | } 68 | else { 69 | throw Abort(.badRequest, reason: "No id given.") 70 | } 71 | } 72 | 73 | func delete(_ request: Request)throws -> EventLoopFuture { 74 | if let id = request.parameters.get("id", as: Int.self) 75 | { 76 | return Product.query(on: request.db).filter(\.$id == id).delete().map { _ in 77 | return .ok 78 | } 79 | } 80 | else { 81 | throw Abort(.badRequest, reason: "No id given.") 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Chapter 9/ProductService-Postgres/Sources/App/Controllers/ProductController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentKit 3 | import Vapor 4 | 5 | final class ProductsController { 6 | 7 | func get(_ request: Request)throws -> EventLoopFuture<[ProductResponse]> { 8 | let querybuilder = Product.query(on: request.db) 9 | 10 | do { 11 | if let categoryId = try request.query.get(Int?.self, at: "categoryId") { 12 | querybuilder.filter(\.$categoryId == categoryId) 13 | } 14 | } 15 | catch {} 16 | 17 | do { 18 | if let query = try request.query.get(String?.self, at: "query") { 19 | querybuilder.filter(\.$name ~~ query) 20 | querybuilder.group(.or) { 21 | $0.filter(\Product.$name ~~ query).filter(\Product.$description ~~ query) 22 | } 23 | } 24 | } 25 | catch {} 26 | 27 | do { 28 | if let idsString = try request.query.get(String?.self, at: "ids") { 29 | let ids:[Int] = idsString.split(separator: ",").map { Int(String($0)) ?? 0 } 30 | querybuilder.filter(\.$id ~~ ids) 31 | } 32 | } 33 | catch {} 34 | 35 | return querybuilder.all().map { products in 36 | return products.map { ProductResponse(id: $0.id!, name: $0.name, description: $0.description, price: $0.price) } 37 | } 38 | } 39 | 40 | func new(_ request: Request)throws -> EventLoopFuture { 41 | let input = try request.content.decode(ProductInput.self) 42 | let product = Product(name: input.name, description: input.description, price: input.price, categoryId: input.categoryId) 43 | return product.save(on: request.db).map { _ in 44 | return ProductResponse(id: product.id!, name: product.name, description: product.description, price: product.price) 45 | } 46 | } 47 | 48 | func edit(_ request: Request)throws -> EventLoopFuture { 49 | let input = try request.content.decode(ProductInput.self) 50 | if let id = request.parameters.get("id", as: Int.self) 51 | { 52 | return Product.query(on: request.db).filter(\.$id == id).all().flatMap { products in 53 | if products.count == 0 { 54 | return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "No product. found!")) 55 | } 56 | let product = products.first! 57 | 58 | product.name = input.name 59 | product.description = input.description 60 | product.price = input.price 61 | product.categoryId = input.categoryId 62 | 63 | return product.save(on: request.db).map { _ in 64 | return ProductResponse(id: product.id!, name: product.name, description: product.description, price: product.price) 65 | } 66 | } 67 | } 68 | else { 69 | throw Abort(.badRequest, reason: "No id given.") 70 | } 71 | } 72 | 73 | func delete(_ request: Request)throws -> EventLoopFuture { 74 | if let id = request.parameters.get("id", as: Int.self) 75 | { 76 | return Product.query(on: request.db).filter(\.$id == id).delete().map { _ in 77 | return .ok 78 | } 79 | } 80 | else { 81 | throw Abort(.badRequest, reason: "No id given.") 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Chapter 15/users/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "async-http-client", 6 | "repositoryURL": "https://github.com/swift-server/async-http-client.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "48e284d1ea6d0e8baac1af1c4ad8bd298670caf6", 10 | "version": "1.0.1" 11 | } 12 | }, 13 | { 14 | "package": "async-kit", 15 | "repositoryURL": "https://github.com/vapor/async-kit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "7457413e57dbfac762b32dd30c1caf2c55a02a3d", 19 | "version": "1.2.0" 20 | } 21 | }, 22 | { 23 | "package": "console-kit", 24 | "repositoryURL": "https://github.com/vapor/console-kit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "7cf8185ad62d50ae9777ce78bcfde8f5c9f900e2", 28 | "version": "4.2.1" 29 | } 30 | }, 31 | { 32 | "package": "fluent", 33 | "repositoryURL": "https://github.com/vapor/fluent.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "e681c93df3201a2d8ceef15e8a9a0634578df233", 37 | "version": "4.0.0" 38 | } 39 | }, 40 | { 41 | "package": "fluent-kit", 42 | "repositoryURL": "https://github.com/vapor/fluent-kit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "ef2a9a76c48856f0388ee2026d67d2ebde8c571c", 46 | "version": "1.7.3" 47 | } 48 | }, 49 | { 50 | "package": "fluent-sqlite-driver", 51 | "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "6f29f6f182c812075f09c7575c18ac5535c26824", 55 | "version": "4.0.1" 56 | } 57 | }, 58 | { 59 | "package": "routing-kit", 60 | "repositoryURL": "https://github.com/vapor/routing-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "4cf052b78aebaf1b23f2264ce04d57b4b6eb5254", 64 | "version": "4.2.0" 65 | } 66 | }, 67 | { 68 | "package": "sql-kit", 69 | "repositoryURL": "https://github.com/vapor/sql-kit.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "ea9928b7f4a801b175a00b982034d9c54ecb6167", 73 | "version": "3.7.0" 74 | } 75 | }, 76 | { 77 | "package": "sqlite-kit", 78 | "repositoryURL": "https://github.com/vapor/sqlite-kit.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "2ec279b9c845cec254646834b66338551a024561", 82 | "version": "4.0.2" 83 | } 84 | }, 85 | { 86 | "package": "sqlite-nio", 87 | "repositoryURL": "https://github.com/vapor/sqlite-nio.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "2e9ae8aab4f20a372901047e0aaf051783eb8bd7", 91 | "version": "1.0.0" 92 | } 93 | }, 94 | { 95 | "package": "swift-backtrace", 96 | "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "eaf2cef011c0c23d1701aa60b364def8015dc3c7", 100 | "version": "1.1.1" 101 | } 102 | }, 103 | { 104 | "package": "swift-crypto", 105 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "9b9d1868601a199334da5d14f4ab2d37d4f8d0c5", 109 | "version": "1.0.2" 110 | } 111 | }, 112 | { 113 | "package": "swift-log", 114 | "repositoryURL": "https://github.com/apple/swift-log.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", 118 | "version": "1.2.0" 119 | } 120 | }, 121 | { 122 | "package": "swift-metrics", 123 | "repositoryURL": "https://github.com/apple/swift-metrics.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "708b960b4605abb20bc55d65abf6bad607252200", 127 | "version": "2.0.0" 128 | } 129 | }, 130 | { 131 | "package": "swift-nio", 132 | "repositoryURL": "https://github.com/apple/swift-nio.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "5fc24345f92ec4c274121776c215ab0aa1ed4d50", 136 | "version": "2.22.0" 137 | } 138 | }, 139 | { 140 | "package": "swift-nio-extras", 141 | "repositoryURL": "https://github.com/apple/swift-nio-extras.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "53808818c2015c45247cad74dc05c7a032c96a2f", 145 | "version": "1.3.2" 146 | } 147 | }, 148 | { 149 | "package": "swift-nio-http2", 150 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "1e68e51752be0b43c5a0ef35818c1dd24d13e77c", 154 | "version": "1.14.1" 155 | } 156 | }, 157 | { 158 | "package": "swift-nio-ssl", 159 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "b75ffaba05b2cffdb1420d558f1a90b4e6c46dcc", 163 | "version": "2.5.0" 164 | } 165 | }, 166 | { 167 | "package": "vapor", 168 | "repositoryURL": "https://github.com/vapor/vapor.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "7bd7ea68df4d36e99b4eba8bba969307e1cdd33a", 172 | "version": "4.15.0" 173 | } 174 | }, 175 | { 176 | "package": "websocket-kit", 177 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "b0736014be634475dac4c23843811257d86dcdc1", 181 | "version": "2.1.1" 182 | } 183 | } 184 | ] 185 | }, 186 | "version": 1 187 | } 188 | -------------------------------------------------------------------------------- /Chapter 14/ExampleVaporApp/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "async-http-client", 6 | "repositoryURL": "https://github.com/swift-server/async-http-client.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "48e284d1ea6d0e8baac1af1c4ad8bd298670caf6", 10 | "version": "1.0.1" 11 | } 12 | }, 13 | { 14 | "package": "async-kit", 15 | "repositoryURL": "https://github.com/vapor/async-kit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "7457413e57dbfac762b32dd30c1caf2c55a02a3d", 19 | "version": "1.2.0" 20 | } 21 | }, 22 | { 23 | "package": "console-kit", 24 | "repositoryURL": "https://github.com/vapor/console-kit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "7cf8185ad62d50ae9777ce78bcfde8f5c9f900e2", 28 | "version": "4.2.1" 29 | } 30 | }, 31 | { 32 | "package": "fluent", 33 | "repositoryURL": "https://github.com/vapor/fluent.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "e681c93df3201a2d8ceef15e8a9a0634578df233", 37 | "version": "4.0.0" 38 | } 39 | }, 40 | { 41 | "package": "fluent-kit", 42 | "repositoryURL": "https://github.com/vapor/fluent-kit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "ef2a9a76c48856f0388ee2026d67d2ebde8c571c", 46 | "version": "1.7.3" 47 | } 48 | }, 49 | { 50 | "package": "fluent-sqlite-driver", 51 | "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "6f29f6f182c812075f09c7575c18ac5535c26824", 55 | "version": "4.0.1" 56 | } 57 | }, 58 | { 59 | "package": "routing-kit", 60 | "repositoryURL": "https://github.com/vapor/routing-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "4cf052b78aebaf1b23f2264ce04d57b4b6eb5254", 64 | "version": "4.2.0" 65 | } 66 | }, 67 | { 68 | "package": "sql-kit", 69 | "repositoryURL": "https://github.com/vapor/sql-kit.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "ea9928b7f4a801b175a00b982034d9c54ecb6167", 73 | "version": "3.7.0" 74 | } 75 | }, 76 | { 77 | "package": "sqlite-kit", 78 | "repositoryURL": "https://github.com/vapor/sqlite-kit.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "2ec279b9c845cec254646834b66338551a024561", 82 | "version": "4.0.2" 83 | } 84 | }, 85 | { 86 | "package": "sqlite-nio", 87 | "repositoryURL": "https://github.com/vapor/sqlite-nio.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "2e9ae8aab4f20a372901047e0aaf051783eb8bd7", 91 | "version": "1.0.0" 92 | } 93 | }, 94 | { 95 | "package": "swift-backtrace", 96 | "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "eaf2cef011c0c23d1701aa60b364def8015dc3c7", 100 | "version": "1.1.1" 101 | } 102 | }, 103 | { 104 | "package": "swift-crypto", 105 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "9b9d1868601a199334da5d14f4ab2d37d4f8d0c5", 109 | "version": "1.0.2" 110 | } 111 | }, 112 | { 113 | "package": "swift-log", 114 | "repositoryURL": "https://github.com/apple/swift-log.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", 118 | "version": "1.2.0" 119 | } 120 | }, 121 | { 122 | "package": "swift-metrics", 123 | "repositoryURL": "https://github.com/apple/swift-metrics.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "708b960b4605abb20bc55d65abf6bad607252200", 127 | "version": "2.0.0" 128 | } 129 | }, 130 | { 131 | "package": "swift-nio", 132 | "repositoryURL": "https://github.com/apple/swift-nio.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "5fc24345f92ec4c274121776c215ab0aa1ed4d50", 136 | "version": "2.22.0" 137 | } 138 | }, 139 | { 140 | "package": "swift-nio-extras", 141 | "repositoryURL": "https://github.com/apple/swift-nio-extras.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "53808818c2015c45247cad74dc05c7a032c96a2f", 145 | "version": "1.3.2" 146 | } 147 | }, 148 | { 149 | "package": "swift-nio-http2", 150 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "1e68e51752be0b43c5a0ef35818c1dd24d13e77c", 154 | "version": "1.14.1" 155 | } 156 | }, 157 | { 158 | "package": "swift-nio-ssl", 159 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "b75ffaba05b2cffdb1420d558f1a90b4e6c46dcc", 163 | "version": "2.5.0" 164 | } 165 | }, 166 | { 167 | "package": "vapor", 168 | "repositoryURL": "https://github.com/vapor/vapor.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "7bd7ea68df4d36e99b4eba8bba969307e1cdd33a", 172 | "version": "4.15.0" 173 | } 174 | }, 175 | { 176 | "package": "websocket-kit", 177 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "b0736014be634475dac4c23843811257d86dcdc1", 181 | "version": "2.1.1" 182 | } 183 | } 184 | ] 185 | }, 186 | "version": 1 187 | } 188 | --------------------------------------------------------------------------------