diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2d9f16e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.build/ +.swiftpm/ diff --git a/.env b/.env new file mode 100644 index 0000000..3ac76e7 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +HOST_NAME="127.0.0.1" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_fallback \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..fdafcef --- /dev/null +++ b/.env.development @@ -0,0 +1,8 @@ +HOST_NAME="127.0.0.1" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_dev + +DATABASE_USERNAME=vapor_username_dev +DATABASE_PASSWORD=vapor_password_dev diff --git a/.env.development.custom_name b/.env.development.custom_name new file mode 100644 index 0000000..3707d59 --- /dev/null +++ b/.env.development.custom_name @@ -0,0 +1,8 @@ +HOST_NAME="127.0.0.1" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_dev_custom + +DATABASE_USERNAME=vapor_username_dev_custom +DATABASE_PASSWORD=vapor_password_dev_custom diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..1be5609 --- /dev/null +++ b/.env.production @@ -0,0 +1,5 @@ +HOST_NAME="www.production.com" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_prod diff --git a/.env.production.custom_name b/.env.production.custom_name new file mode 100644 index 0000000..4e9d530 --- /dev/null +++ b/.env.production.custom_name @@ -0,0 +1,5 @@ +HOST_NAME="www.production.custom.com" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_prod_custom diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..7e48efc --- /dev/null +++ b/.env.staging @@ -0,0 +1,5 @@ +HOST_NAME="www.staging.com" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_stag diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..df537da --- /dev/null +++ b/.env.testing @@ -0,0 +1,8 @@ +HOST_NAME="www.tesing.com" +HOST_PORT=8080 + +DATABASE_HOST=localhost +DATABASE_NAME=vapor_database_test + +DATABASE_USERNAME=vapor_username_test +DATABASE_PASSWORD=vapor_password_test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c56b687 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Packages +.build +xcuserdata +*.xcodeproj +DerivedData/ +.DS_Store +db.sqlite +.swiftpm +.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4ca2dd0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +language: generic +matrix: + include: + - os: osx + osx_image: xcode11.5 + env: SCHEME="Run" + + +before_install: + - gem install xcpretty + - brew tap vapor/tap + - brew update + - brew install vapor + + +script: + - swift build -Xswiftc -g -c debug + - swift package generate-xcodeproj + - xcodebuild -scheme $SCHEME -enableCodeCoverage YES test | xcpretty + + +after_success: + - bash <(curl -s https://codecov.io/bash) + +# +deploy: + provider: releases + api_key: + secure: Y1fkSiMXxU0wJEZ12PtfuWheTcIFD870iHpCRollKyVjobI50MF0yYWHm8jMtwHCYLKwmf+SZwp/w+emRZ6iho1n5yCo+zPeAcZSc2XCi7pzGzJ5kKvi3Lgh01BduTUI30+LqJ7XNk2HXH61Zl2eBZl4hFn6+Bc5BmCNyue2PKs2gVexpTub7X3abhdCR6frbdtC3lNW2HWiH/j4ccDE6Lmr1WJjFgM2QzoXVc56wWaUlXwOBPAmIG8AZM2nN7wKqV7znMJTNnE+hw7e1vtdLaV4QrNDDkya4f3Z1mgAJCNDQg8sIERvqdAg4NOh3Y090HR5QYHp8WejrvbKg/54KIdTE0L7TzLeYARG2NrqA0o4IezLGvbmlDL6vnwegNhSHp1IK89ZN2he/Tt4t0ONrcyKb7V4N6s5LKxzI24pvAjwG3pdqdO57iXus3CMksxaNW4F9ZaWaObSSi8zqFLqgyx8ZpTjy7zTso8xKyO/+/BpaD1BF8NJCzHKqgQmWbypyo4tNlRtyWsli7Eo2xcrEwTPU07CbYNbUnRfEj5hHc+Or/V0T6hgSnywaNs9crU9bedOn8Xh8myPamV17TGr282f7pwqCyIg+wms13PyhV5inNM7B7qbufXzQnM+Zu3OrBqKEVD7A1H3QxoLLhGJNe3gQKuFjXcC5Y/q68Y0WqQ= + file: ".build/debug/Run" + on: + tags: true + branch: master + repo: Guang1234567/vapor4_auth_template + skip_cleanup: 'true' + draft: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3203d24 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# ================================ +# Build image +# ================================ +FROM swift:5.2-bionic as build +WORKDIR /build + +# First just resolve dependencies. +# This creates a cached layer that can be reused +# as long as your Package.swift/Package.resolved +# files do not change. +COPY ./Package.* ./ +RUN swift package resolve + +# Copy entire repo into container +COPY . . + +# Compile with optimizations +RUN swift build --enable-test-discovery -c release + +# ================================ +# Run image +# ================================ +FROM swift:5.2-bionic-slim + +# Create a vapor user and group with /app as its home directory +RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor + +# Switch to the new home directory +WORKDIR /app + +# Copy build artifacts +COPY --from=build --chown=vapor:vapor /build/.build/release /app +# Uncomment the next line if you need to load resources from the `Public` directory +#COPY --from=build --chown=vapor:vapor /build/Public /app/Public + +# Ensure all further commands run as the vapor user +USER vapor:vapor + +# Start the Vapor service when the image is run, default to listening on 8080 in production environment +ENTRYPOINT ["./Run"] +CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..46db0e6 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,223 @@ +{ + "object": { + "pins": [ + { + "package": "async-http-client", + "repositoryURL": "https://github.com/swift-server/async-http-client.git", + "state": { + "branch": null, + "revision": "037b70291941fe43de668066eb6fb802c5e181d2", + "version": "1.1.1" + } + }, + { + "package": "async-kit", + "repositoryURL": "https://github.com/vapor/async-kit.git", + "state": { + "branch": null, + "revision": "8e2cad80dd9f62f0349a2a78ab8c81fb456f4d4a", + "version": "1.1.1" + } + }, + { + "package": "console-kit", + "repositoryURL": "https://github.com/vapor/console-kit.git", + "state": { + "branch": null, + "revision": "05c52a91631a6ca432275439f6456a5dace0d1db", + "version": "4.1.2" + } + }, + { + "package": "fluent", + "repositoryURL": "https://github.com/vapor/fluent.git", + "state": { + "branch": null, + "revision": "e681c93df3201a2d8ceef15e8a9a0634578df233", + "version": "4.0.0" + } + }, + { + "package": "fluent-kit", + "repositoryURL": "https://github.com/vapor/fluent-kit.git", + "state": { + "branch": null, + "revision": "ad5eef38c5168fdb4b77d5a73794a90e0ce3b9a5", + "version": "1.0.2" + } + }, + { + "package": "fluent-postgres-driver", + "repositoryURL": "https://github.com/vapor/fluent-postgres-driver.git", + "state": { + "branch": null, + "revision": "cf8ec53b87606dc799a07a0754a297b32cf9f592", + "version": "2.0.0" + } + }, + { + "package": "postgres-kit", + "repositoryURL": "https://github.com/vapor/postgres-kit.git", + "state": { + "branch": null, + "revision": "fca27ff90326e3612ed0197598a7ce467bfedfbd", + "version": "2.1.0" + } + }, + { + "package": "postgres-nio", + "repositoryURL": "https://github.com/vapor/postgres-nio.git", + "state": { + "branch": null, + "revision": "25aa31fdd55ea119edee270ce4bd7bd4bd59fdf7", + "version": "1.1.0" + } + }, + { + "package": "routing-kit", + "repositoryURL": "https://github.com/vapor/routing-kit.git", + "state": { + "branch": null, + "revision": "e7f2d5bd36dc65a9edb303541cb678515a7fece3", + "version": "4.1.0" + } + }, + { + "package": "RxSwift", + "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", + "state": { + "branch": null, + "revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa", + "version": "5.1.1" + } + }, + { + "package": "sql-kit", + "repositoryURL": "https://github.com/vapor/sql-kit.git", + "state": { + "branch": null, + "revision": "8b82edd5d918068f204e3ac4206d5d15f6740395", + "version": "3.5.0" + } + }, + { + "package": "swift-backtrace", + "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", + "state": { + "branch": null, + "revision": "f2fd8c4845a123419c348e0bc4b3839c414077d5", + "version": "1.2.0" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "9b9d1868601a199334da5d14f4ab2d37d4f8d0c5", + "version": "1.0.2" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", + "version": "1.2.0" + } + }, + { + "package": "swift-metrics", + "repositoryURL": "https://github.com/apple/swift-metrics.git", + "state": { + "branch": null, + "revision": "708b960b4605abb20bc55d65abf6bad607252200", + "version": "2.0.0" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "120acb15c39aa3217e9888e515de160378fbcc1e", + "version": "2.18.0" + } + }, + { + "package": "swift-nio-extras", + "repositoryURL": "https://github.com/apple/swift-nio-extras.git", + "state": { + "branch": null, + "revision": "7cd24c0efcf9700033f671b6a8eaa64a77dd0b72", + "version": "1.5.1" + } + }, + { + "package": "swift-nio-http2", + "repositoryURL": "https://github.com/apple/swift-nio-http2.git", + "state": { + "branch": null, + "revision": "c5d10f4165128c3d0cc0e3c0f0a8ef55947a73a6", + "version": "1.12.2" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "f0b118d9af6c4e78bc4f3f4fbb464020172b0bf4", + "version": "2.7.5" + } + }, + { + "package": "Swift_Atomics", + "repositoryURL": "https://github.com/Guang1234567/Swift_Atomics.git", + "state": { + "branch": "master", + "revision": "32a8c89a132c5d6d4330002dc65af2953306d3b6", + "version": null + } + }, + { + "package": "Swift_Boost_Context", + "repositoryURL": "https://github.com/Guang1234567/Swift_Boost_Context.git", + "state": { + "branch": "master", + "revision": "cdc7565f77c5308db073036d0e03277dcb0f9e1b", + "version": null + } + }, + { + "package": "Swift_Coroutine_NIO2", + "repositoryURL": "https://github.com/Guang1234567/Swift_Coroutine_NIO2.git", + "state": { + "branch": "master", + "revision": "1ce084dd0abc4c1331cb81b6f010d7d6db23605f", + "version": null + } + }, + { + "package": "vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "dc2aa1e02e04a47b67cb0dabed628fe844900f30", + "version": "4.14.0" + } + }, + { + "package": "websocket-kit", + "repositoryURL": "https://github.com/vapor/websocket-kit.git", + "state": { + "branch": null, + "revision": "021edd1ca55451ad15b3e84da6b4064e4b877b34", + "version": "2.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..69fecd7 --- /dev/null +++ b/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "demo_swift_server_vapor_auth", + platforms: [ + .macOS(.v10_15) + ], + dependencies: [ + // 💧 A server-side Swift web framework. + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), + .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), + .package(url: "https://github.com/Guang1234567/Swift_Coroutine_NIO2.git", .branch("master")) + ], + targets: [ + .target( + name: "App", + dependencies: [ + .product(name: "Fluent", package: "fluent"), + .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), + .product(name: "Vapor", package: "vapor"), + "Swift_Coroutine_NIO2" + ], + swiftSettings: [ + // Enable better optimizations when building in Release configuration. Despite the use of + // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release + // builds. See for details. + .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) + ] + ), + .target(name: "Run", dependencies: [.target(name: "App")]), + .testTarget(name: "AppTests", dependencies: [ + .target(name: "App"), + .product(name: "XCTVapor", package: "vapor"), + ]) + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..25d5bcb --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +

+ Auth Template +
+
+ + Documentation + + + Team Chat + + + MIT License + + + Continuous Integration + + + Swift 4.1 + +

+ + + +## Usage + +```bash +# 1) + +brew install vapor + +# 2) + +vapor new demo_swift_server_vapor_auth --template https://github.com/Guang1234567/vapor4_auth_template +``` + +## Develop on `localhost` + +### macOS + +1) Serving `HTTPS` by `nginx` + + 1) install `nginx` + + https://docs.vapor.codes/4.0/deploy/nginx/ + + ```bash + brew install nginx + ``` + + 2) cfg ngnix + + https://github.com/Guang1234567/vapor4_auth_template/blob/82953c8f1d70a335ca0dbe8541965f5fc2851587/etc/nginx/nginx.conf#L117-L153 + + 3) start `nginx` + + ```bash + nginx -c ~/dev_kit/workspace/demo_swift_server_vapor_auth/etc/nginx/nginx.conf + ``` + + 4) check `HTTPS` Env + + Using your smartphone which under the same wifi with your macbookpro to browse: + + ```bash + http://:8080/ + + + For example (Dont copy!): + http://10.0.0.28:8080/ + ``` + + then will see: + + ```html + Welcome to nginx! + + If you see this page, the nginx web server is successfully installed and working. Further configuration is required. + + For online documentation and support please refer to nginx.org. + Commercial support is available at nginx.com. + + Thank you for using nginx. + ``` + +2) Start vapor web application and serving on `http://127.0.0.1:8080` + + ```bash + swift build -Xswiftc -g -c debug && .build/debug/Run --log debug --env development.custom_name + ``` + + > note: + > + > [issue: Crashed when calling Bcrypt.hash](https://github.com/vapor/vapor/issues/2229#issuecomment-653721292) on + > + > - macOS catalina 10.15.6 with xcode 11.6 + > - macOS catalina 10.15.5 with xcode 11.5 + + +then access vapor web application via `HTTPS` + + - on smartphone + + ```bash + https://10.0.0.28/ + + or + + https://10.0.0.28:443/ + ``` + + - on PC + + ```bash + https://127.0.0.1/ + + or + + https://127.0.0.1:443/ + ``` + + then both will see: + + ```html + It works! + ``` + +3) Test API via CLion with `swift plugin` + +https://github.com/Guang1234567/vapor4_auth_template/blob/82953c8f1d70a335ca0dbe8541965f5fc2851587/Tests/AppRestApiTests/Controllers/UserController.http#L1-L9 + +### Ubuntu + +// in the process ... ( I dont have the Ubuntu -_-|| ) \ No newline at end of file diff --git a/Sources/App/Controllers/.gitkeep b/Sources/App/Controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Sources/App/Controllers/EnvController.swift b/Sources/App/Controllers/EnvController.swift new file mode 100644 index 0000000..d9eb1c9 --- /dev/null +++ b/Sources/App/Controllers/EnvController.swift @@ -0,0 +1,38 @@ +import Vapor + +struct EnvController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let env = routes.grouped("env") + env.get(use: index) + + } + + func index(req: Request) throws -> String { + let envDetail: String = + """ + + https://docs.vapor.codes/4.0/environment/ + + \(req.application.environment) + + Environment Variables: + + DATABASE_HOST = \(String(describing:Environment.process.DATABASE_HOST)) + DATABASE_NAME = \(String(describing:Environment.process.DATABASE_NAME)) + + """ + + let processEnv: Environment = req.application.environment + //let processInfo: ProcessInfo = .processInfo + //req.logger.info("Environment Variables:\n") + //req.logger.info("\(processInfo.environment)") + if processEnv.name.contains(Environment.production.name) { + req.logger.notice("\(envDetail)") + } else { + req.logger.info("\(envDetail)") + } + + return envDetail + } + +} diff --git a/Sources/App/Controllers/GalaxyController.swift b/Sources/App/Controllers/GalaxyController.swift new file mode 100644 index 0000000..23e9571 --- /dev/null +++ b/Sources/App/Controllers/GalaxyController.swift @@ -0,0 +1,33 @@ +import Fluent +import Vapor + +struct GalaxyController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let galaxies = routes.grouped("galaxies") + galaxies.get(use: index) + galaxies.post(use: create) + galaxies.group(":galaxyID") { galaxy in + galaxy.delete(use: delete) + } + } + + func index(req: Request) throws -> EventLoopFuture<[Galaxy]> { + return Galaxy.query(on: req.db).with(\.$stars).all() + } + + func create(req: Request) throws -> EventLoopFuture { + let galaxy = try req.content.decode(Galaxy.self) + return galaxy.save(on: req.db).map { + galaxy + } + } + + func delete(req: Request) throws -> EventLoopFuture { + return Galaxy.find(req.parameters.get("galaxyID"), on: req.db) + .unwrap(or: Abort(.notFound)) + .flatMap { + $0.delete(on: req.db) + } + .transform(to: .ok) + } +} diff --git a/Sources/App/Controllers/StarController.swift b/Sources/App/Controllers/StarController.swift new file mode 100644 index 0000000..95fdaf9 --- /dev/null +++ b/Sources/App/Controllers/StarController.swift @@ -0,0 +1,33 @@ +import Fluent +import Vapor + +struct StarController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let stars = routes.grouped("stars") + stars.get(use: index) + stars.post(use: create) + stars.group(":starID") { star in + star.delete(use: delete) + } + } + + func index(req: Request) throws -> EventLoopFuture<[Star]> { + return Star.query(on: req.db).all() + } + + func create(req: Request) throws -> EventLoopFuture { + let galaxy = try req.content.decode(Star.self) + return galaxy.save(on: req.db).map { + galaxy + } + } + + func delete(req: Request) throws -> EventLoopFuture { + return Star.find(req.parameters.get("starID"), on: req.db) + .unwrap(or: Abort(.notFound)) + .flatMap { + $0.delete(on: req.db) + } + .transform(to: .ok) + } +} diff --git a/Sources/App/Controllers/TodoController.swift b/Sources/App/Controllers/TodoController.swift new file mode 100644 index 0000000..483bd72 --- /dev/null +++ b/Sources/App/Controllers/TodoController.swift @@ -0,0 +1,33 @@ +import Fluent +import Vapor + +struct TodoController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let todos = UserController.tokenProtected(routes).grouped("todos") + todos.get(use: index) + todos.post(use: create) + todos.group(":todoID") { todo in + todo.delete(use: delete) + } + } + + func index(req: Request) throws -> EventLoopFuture<[Todo]> { + return Todo.query(on: req.db).all() + } + + func create(req: Request) throws -> EventLoopFuture { + let todo = try req.content.decode(Todo.self) + return todo.save(on: req.db).map { + todo + } + } + + func delete(req: Request) throws -> EventLoopFuture { + return Todo.find(req.parameters.get("todoID"), on: req.db) + .unwrap(or: Abort(.notFound)) + .flatMap { + $0.delete(on: req.db) + } + .transform(to: .ok) + } +} diff --git a/Sources/App/Controllers/UserController.swift b/Sources/App/Controllers/UserController.swift new file mode 100644 index 0000000..f8c4046 --- /dev/null +++ b/Sources/App/Controllers/UserController.swift @@ -0,0 +1,230 @@ +import Fluent +import Vapor +import Swift_Coroutine_NIO2 + + +/// Creates new users and logs them in. +struct UserController: RouteCollection { + + static func passwordProtected(_ routes: RoutesBuilder) -> RoutesBuilder { + routes.grouped(User.authenticator()) + .grouped(User.guardMiddleware()) + } + + static func passwordAndTokenProtected(_ routes: RoutesBuilder) -> RoutesBuilder { + routes.grouped(User.authenticator()) + .grouped(UserToken.authenticator()) + .grouped(User.guardMiddleware()) + } + + static func tokenProtected(_ routes: RoutesBuilder) -> RoutesBuilder { + routes.grouped(UserToken.authenticator()) + .grouped(User.guardMiddleware()) + } + + static func sessionProtected(_ routes: RoutesBuilder) -> RoutesBuilder { + routes.grouped(User.sessionAuthenticator()) + } + + func boot(routes: RoutesBuilder) throws { + let users = routes.grouped("users") + users.post(use: create) + + // basic / password auth protected routes + /* + let passwordProtected = UserController.passwordProtected(routes) + passwordProtected.post("login", use: login) + */ + + // https://docs.vapor.codes/4.0/authentication/#composing-methods + // This composition of authenticators results in a route that can be accessed by either password or token. + // Such a route could allow a user to login and generate a token, + // then continue to use that token to generate new tokens + let passwordAndTokenProtected = UserController.passwordAndTokenProtected(routes) + passwordAndTokenProtected.post("login", use: login) + + // bearer / token auth protected routes + let tokenProtected = UserController.tokenProtected(routes) + tokenProtected.post("logout", use: logout) + tokenProtected.get("me", use: me) + } + + func create(req: Request) throws -> EventLoopFuture { + + try User.Create.validate(req) + let create = try req.content.decode(User.Create.self) + guard create.password == create.confirmPassword else { + throw Abort(.badRequest, reason: "Passwords did not match") + } + + return req.password.async + .hash(create.password) + .map { (digest: String) -> User in + User( + name: create.name, + email: create.email, + passwordHash: digest + ) + } + .flatMap { user in + user.save(on: req.db) + .flatMapThrowing { + try User.Get( + id: user.requireID(), + name: user.name, + email: user.email) + } + } + } + + /* + func login(req: Request) throws -> EventLoopFuture { + let user = try req.auth.require(User.self) + let userTokenBeDeleted = req.auth.get(UserToken.self) + + return req.application.threadPool + .runIfActive(eventLoop: req.eventLoop, user.generateToken) + .flatMap { token in + let saveToken = token.save(on: req.db) + .map { + token + } + + if let userTokenBeDeleted = userTokenBeDeleted { + return UserToken.query(on: req.db) + .filter(\.$value == userTokenBeDeleted.value) + .delete(force: true) + .flatMap { + saveToken + } + } else { + return saveToken + } + } + } + */ + + func login(req: Request) throws -> EventLoopFuture { + + let user = try req.auth.require(User.self) + let userTokenBeDeleted = req.auth.get(UserToken.self) + + let eventLoop = req.eventLoop + let ioThreadPool = req.application.threadPool + return EventLoopFuture.coroutine(eventLoop) { co in + + try co.continueOn(ioThreadPool) + + let token = try user.generateToken() + + try co.continueOn(eventLoop) + + if let userTokenBeDeleted = userTokenBeDeleted { + try UserToken.query(on: req.db) + .filter(\.$value == userTokenBeDeleted.value) + .delete(force: true) + .await(co) + } + + return try token.save(on: req.db) + .map { + token + } + .await(co) + } + } + + func logout(req: Request) throws -> EventLoopFuture { + let user = try req.auth.require(User.self) + let userTokenBeDeleted = try req.auth.require(UserToken.self) + req.auth.logout(User.self) + + return UserToken.query(on: req.db) + .filter(\.$value == userTokenBeDeleted.value) + .delete(force: true) + .flatMapThrowing { + try UserToken( + id: userTokenBeDeleted.id, + value: userTokenBeDeleted.value, + userID: userTokenBeDeleted.user.requireID()) + } + } + + func me(req: Request) throws -> EventLoopFuture { + let myself = try req.auth.require(User.self) + return try req.eventLoop.makeSucceededFuture( + User.Get(id: myself.requireID(), + name: myself.name, + email: myself.email) + ) + } +} + + +extension User { + + /// DTO : for creating(registing) a user + struct Create: Content { + var name: String + var email: String + var password: String + var confirmPassword: String + } + + /// DTO : for avoiding response sensitive field (eg. User.passwordHash) + struct Get: Content { + var id: UUID + var name: String + var email: String + } + +} + + +extension User.Create: Validatable { + + static func validations(_ validations: inout Validations) { + validations.add("name", as: String.self, is: !.empty) + validations.add("email", as: String.self, is: .email) + validations.add("password", as: String.self, is: .count(6...)) + } + +} + + +extension User: ModelAuthenticatable { + static let usernameKey = \User.$email + static let passwordHashKey = \User.$passwordHash + + func verify(password: String) throws -> Bool { + try Bcrypt.verify(password, created: self.passwordHash) + } +} + + +extension User { + func generateToken() throws -> UserToken { + try .init( + value: [UInt8].random(count: 16).base64, + userID: self.requireID() + ) + } +} + + +extension UserToken: ModelTokenAuthenticatable { + static let valueKey = \UserToken.$value + static let userKey = \UserToken.$user + + var isValid: Bool { + guard let expiresAt = self.expiresAt else { + return false + } + + return expiresAt > .init() + } +} + + +// Allow this model to be persisted in sessions. +extension User: ModelSessionAuthenticatable {} diff --git a/Sources/App/Migrations/CreateGalaxy.swift b/Sources/App/Migrations/CreateGalaxy.swift new file mode 100644 index 0000000..32d5a46 --- /dev/null +++ b/Sources/App/Migrations/CreateGalaxy.swift @@ -0,0 +1,14 @@ +import Fluent + +struct CreateGalaxy: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return database.schema(Galaxy.schema) + .id() + .field("name", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + return database.schema(Galaxy.schema).delete() + } +} diff --git a/Sources/App/Migrations/CreateStar.swift b/Sources/App/Migrations/CreateStar.swift new file mode 100644 index 0000000..cbd916d --- /dev/null +++ b/Sources/App/Migrations/CreateStar.swift @@ -0,0 +1,17 @@ +import Fluent + +struct CreateStar: Migration { + // Prepares the database for storing Star models. + func prepare(on database: Database) -> EventLoopFuture { + database.schema(Star.schema) + .id() + .field("name", .string, .required) + .field("galaxy_id", .uuid, .references(Galaxy.schema, "id")) + .create() + } + + // Optionally reverts the changes made in the prepare method. + func revert(on database: Database) -> EventLoopFuture { + database.schema(Star.schema).delete() + } +} diff --git a/Sources/App/Migrations/CreateTodo.swift b/Sources/App/Migrations/CreateTodo.swift new file mode 100644 index 0000000..ece3e94 --- /dev/null +++ b/Sources/App/Migrations/CreateTodo.swift @@ -0,0 +1,14 @@ +import Fluent + +struct CreateTodo: Migration { + func prepare(on database: Database) -> EventLoopFuture { + return database.schema(Todo.schema) + .id() + .field("title", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + return database.schema(Todo.schema).delete() + } +} diff --git a/Sources/App/Migrations/CreateUser.swift b/Sources/App/Migrations/CreateUser.swift new file mode 100644 index 0000000..5f0c7e2 --- /dev/null +++ b/Sources/App/Migrations/CreateUser.swift @@ -0,0 +1,18 @@ +import Fluent +import Vapor + +struct CreateUser: Migration { + + func prepare(on database: Database) -> EventLoopFuture { + database.schema(User.schema) + .id() + .field("name", .string, .required) + .field("email", .string, .required) + .field("password_hash", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(User.schema).delete() + } +} \ No newline at end of file diff --git a/Sources/App/Migrations/CreateUserToken.swift b/Sources/App/Migrations/CreateUserToken.swift new file mode 100644 index 0000000..fb6ed6d --- /dev/null +++ b/Sources/App/Migrations/CreateUserToken.swift @@ -0,0 +1,22 @@ +import Fluent +import Vapor + +struct CreateUserToken: Migration { + + func prepare(on database: Database) -> EventLoopFuture { + database.schema("user_tokens") + .id() + .field("value", .string, .required) + .field("user_id", .uuid, .required) + .field("created_at", .string) + .field("updated_at", .string) + .field("expires_at", .string) + .unique(on: "value") + .foreignKey("user_id", references: "users", "id", onDelete: .cascade) // auto delete user_token when delete user + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema("user_tokens").delete() + } +} \ No newline at end of file diff --git a/Sources/App/Models/BusinessError.swift b/Sources/App/Models/BusinessError.swift new file mode 100644 index 0000000..a8d5ba1 --- /dev/null +++ b/Sources/App/Models/BusinessError.swift @@ -0,0 +1,52 @@ +import Vapor + +/// Usage: +/// ``` +/// throw BusinessError.userNotLoggedIn.abort() +/// ``` +/// +/// Then will print below output in terminal: +/// ``` +/// [ ERROR ] Abort.401: User is not logged in. [request-id: 38440B66-E235-4B84-A9F1-260E4EDA2BEC] (App/Controllers/EnvController.swift:35) +/// ``` + +enum BusinessError { + + case userNotLoggedIn + + case invalidEmail(String) +} + +extension BusinessError { + + func abort( + file: String = #file, + function: String = #function, + line: UInt = #line, + column: UInt = #column, + stackTrace: StackTrace? = .capture(skip: 1) + ) -> Abort { + switch self { + case .userNotLoggedIn: + return Abort( + .unauthorized, + reason: "User is not logged in.", + file: file, + function: function, + line: line, + column: column, + stackTrace: stackTrace + ) + case .invalidEmail(let email): + return Abort( + .badRequest, + reason: "Email address is not valid: \(email).", + file: file, + function: function, + line: line, + column: column, + stackTrace: stackTrace + ) + } + } +} diff --git a/Sources/App/Models/Env.swift b/Sources/App/Models/Env.swift new file mode 100644 index 0000000..40ab861 --- /dev/null +++ b/Sources/App/Models/Env.swift @@ -0,0 +1,43 @@ +import Vapor + + +enum EnvError: Error { + + case dotEnvDev( + reason: String, + file: String = #file, + function: String = #function, + line: UInt = #line, + column: UInt = #column/*, + stackTrace: StackTrace? = .capture(skip: 1)*/ + ) + + case dotEnvProd( + reason: String, + file: String = #file, + function: String = #function, + line: UInt = #line, + column: UInt = #column/*, + stackTrace: StackTrace? = .capture(skip: 1)*/ + ) + +} + + +extension Environment { + static var staging: Environment { + .custom(name: "staging") + } +} + +extension Environment { + static var dev_custom_name: Environment { + .custom(name: "development.custom_name") + } +} + +extension Environment { + static var prod_custom_name: Environment { + .custom(name: "production.custom_name") + } +} diff --git a/Sources/App/Models/Galaxy.swift b/Sources/App/Models/Galaxy.swift new file mode 100644 index 0000000..c0d090b --- /dev/null +++ b/Sources/App/Models/Galaxy.swift @@ -0,0 +1,25 @@ +import Fluent +import Vapor + + +final class Galaxy: Model, Content { + + static let schema = "galaxies" + + @ID(key: .id) + var id: UUID? + + @Field(key: "name") + var name: String + + @Children(for: \.$galaxy) + var stars: [Star] + + init() {} + + init(id: UUID?, name: String) { + self.id = id + self.name = name + } + +} diff --git a/Sources/App/Models/Star.swift b/Sources/App/Models/Star.swift new file mode 100644 index 0000000..17e9f05 --- /dev/null +++ b/Sources/App/Models/Star.swift @@ -0,0 +1,26 @@ +import Fluent +import Vapor + + +final class Star: Model, Content { + + static let schema = "stars" + + @ID(key: .id) + var id: UUID? + + @Field(key: "name") + var name: String + + @Parent(key: "galaxy_id") + var galaxy: Galaxy + + init() {} + + init(id: UUID?, name: String, galaxyID: UUID) { + self.id = id + self.name = name + self.$galaxy.id = galaxyID + } + +} diff --git a/Sources/App/Models/Todo.swift b/Sources/App/Models/Todo.swift new file mode 100644 index 0000000..6518db2 --- /dev/null +++ b/Sources/App/Models/Todo.swift @@ -0,0 +1,20 @@ +import Fluent +import Vapor + +final class Todo: Model, Content { + + static let schema = "todos" + + @ID(key: .id) + var id: UUID? + + @Field(key: "title") + var title: String + + init() { } + + init(id: UUID? = nil, title: String) { + self.id = id + self.title = title + } +} diff --git a/Sources/App/Models/User.swift b/Sources/App/Models/User.swift new file mode 100644 index 0000000..03a17b1 --- /dev/null +++ b/Sources/App/Models/User.swift @@ -0,0 +1,28 @@ +import Fluent +import Vapor + +final class User: Model, Content { + + static let schema = "users" + + @ID(key: .id) + var id: UUID? + + @Field(key: "name") + var name: String + + @Field(key: "email") + var email: String + + @Field(key: "password_hash") + var passwordHash: String + + init() {} + + init(id: UUID? = nil, name: String, email: String, passwordHash: String) { + self.id = id + self.name = name + self.email = email + self.passwordHash = passwordHash + } +} \ No newline at end of file diff --git a/Sources/App/Models/UserToken.swift b/Sources/App/Models/UserToken.swift new file mode 100644 index 0000000..7addb5c --- /dev/null +++ b/Sources/App/Models/UserToken.swift @@ -0,0 +1,38 @@ +import Fluent +import Vapor + +final class UserToken: Model, Content { + static let schema = "user_tokens" + + @ID(key: .id) + var id: UUID? + + @Field(key: "value") + var value: String + + @Parent(key: "user_id") + var user: User + + @Timestamp(key: "created_at", on: .create, format: .iso8601(withMilliseconds: true)) + var createdAt: Date? + + @Timestamp(key: "updated_at", on: .update, format: .iso8601(withMilliseconds: true)) + var updatedAt: Date? + + @Timestamp(key: "expires_at", on: .delete, format: .iso8601(withMilliseconds: true)) + var expiresAt: Date? + + init() {} + + init(id: UUID? = nil, + value: String, + userID: User.IDValue, + // set token to expire after 5 hours + expiresAt: Date = Date.init(timeInterval: 60 * 60 * 5, since: .init())) { + self.id = id + self.value = value + self.$user.id = userID + self.expiresAt = expiresAt + } +} + diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift new file mode 100644 index 0000000..bd6ac4a --- /dev/null +++ b/Sources/App/configure.swift @@ -0,0 +1,105 @@ +import Fluent +import FluentPostgresDriver +import Vapor + + +// configures your application +public func configure(_ app: Application) throws { + + // Always capture stack traces, regardless of log level. + StackTrace.isCaptureEnabled = true + + // https://docs.vapor.codes/4.0/passwords/#bcrypt + app.passwords.use(.bcrypt) + + try cfgHttpServer(app) + try cfgMiddleware(app) + try cfgDatabase(app) + + // register routes + try routes(app) +} + +private func cfgHttpServer(_ app: Application) throws { + let envName = app.environment.name + + guard let hostname = Environment.process.HOST_NAME else { + throw EnvError.dotEnvDev(reason: "Please configurate `HOST_NAME=XXX` in file('.env.\(envName)') !") + } + + guard let port = Environment.process.HOST_PORT else { + throw EnvError.dotEnvDev(reason: "Please configurate `HOST_PORT=XXX` in file('.env.\(envName)') !") + } + + app.http.server.configuration.hostname = hostname + app.http.server.configuration.port = Int(port) ?? 8080 + + // Enable TLS. + // ----------- + // Disable HTTP/1 support. + /* + app.http.server.configuration.supportVersions = [.two] + try app.http.server.configuration.tlsConfiguration = .forServer( + certificateChain: [ + .certificate(.init( + file: "/path/to/cert.pem", + format: .pem + )) + ], + privateKey: .file("/path/to/key.pem") + ) + */ +} + +private func cfgMiddleware(_ app: Application) throws { // Clear any existing middleware. + app.middleware = .init() + + app.middleware.use(CORSMiddleware(configuration: CORSMiddleware.Configuration( + allowedOrigin: .all, + allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH], + allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent, .accessControlAllowOrigin] + ))) + + app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + app.sessions.use(.fluent) + app.middleware.use(app.sessions.middleware) + + app.middleware.use(ErrorMiddleware.default(environment: app.environment)) +} + +private func cfgDatabase(_ app: Application) throws { + let envName = app.environment.name + + guard let dbHostName = Environment.process.DATABASE_HOST else { + throw EnvError.dotEnvDev(reason: "Please configurate `DATABASE_HOST=XXX` in file('.env.\(envName)') !") + } + + guard let dbName = Environment.process.DATABASE_NAME else { + throw EnvError.dotEnvDev(reason: "Please configurate `DATABASE_NAME=XXX` in file('.env.\(envName)') !") + } + + guard let dbUserName = Environment.process.DATABASE_USERNAME else { + throw EnvError.dotEnvProd(reason: "Please `export DATABASE_USERNAME=XXX` in the terminal !") + } + + guard let dbPassword = Environment.process.DATABASE_PASSWORD else { + throw EnvError.dotEnvProd(reason: "Please `export DATABASE_PASSWORD=XXX` in the terminal !") + } + + app.databases.use( + .postgres( + hostname: dbHostName, + username: dbUserName, + password: dbPassword, + database: dbName + ), + as: .psql) + + app.migrations.add(SessionRecord.migration) + app.migrations.add(CreateTodo()) + app.migrations.add(CreateGalaxy()) + app.migrations.add(CreateStar()) + app.migrations.add(CreateUser()) + app.migrations.add(CreateUserToken()) +} diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift new file mode 100644 index 0000000..f0031c3 --- /dev/null +++ b/Sources/App/routes.swift @@ -0,0 +1,18 @@ +import Fluent +import Vapor + +func routes(_ app: Application) throws { + app.get { req in + return "It works!" + } + + app.get("hello") { req -> String in + return "Hello, world!" + } + + try app.register(collection: EnvController()) + try app.register(collection: UserController()) + try app.register(collection: TodoController()) + try app.register(collection: GalaxyController()) + try app.register(collection: StarController()) +} diff --git a/Sources/Run/main.swift b/Sources/Run/main.swift new file mode 100644 index 0000000..02515c1 --- /dev/null +++ b/Sources/Run/main.swift @@ -0,0 +1,19 @@ +import App +import Vapor + +// vapor run serve --env development.custom_name --log debug +// vapor run serve --env production.custom_name --log info + +// vapor run serve --env development +// vapor run serve --env production +// vapor run serve --env staging +// vapor run serve --env testing + +var env = try Environment.detect() +try LoggingSystem.bootstrap(from: &env) +let app = Application(env) +defer { + app.shutdown() +} +try configure(app) +try app.run() diff --git a/Tests/AppRestApiTests/Controllers/GalaxyController.http b/Tests/AppRestApiTests/Controllers/GalaxyController.http new file mode 100644 index 0000000..9dad2ed --- /dev/null +++ b/Tests/AppRestApiTests/Controllers/GalaxyController.http @@ -0,0 +1,17 @@ +GET http://{{host}}/galaxies HTTP/1.1 +Accept: application/json + +### + +POST http://{{host}}/galaxies HTTP/1.1 +#content-length: 21 +content-type: application/json + +{ + "name": "Milky Way" +} + +### + + + diff --git a/Tests/AppRestApiTests/Controllers/StarController.http b/Tests/AppRestApiTests/Controllers/StarController.http new file mode 100644 index 0000000..bbb9c04 --- /dev/null +++ b/Tests/AppRestApiTests/Controllers/StarController.http @@ -0,0 +1,33 @@ +### +POST http://{{host}}/galaxies HTTP/1.1 +content-type: application/json + +{ + "name": "银河系" +} + +> {% +client.test("Request executed successfully", function() { + client.assert(response.status === 200, "Response status is not 200"); + client.log("\n"); + client.log("galaxy.id = " + response.body.id); + client.log("\n\n"); +}); + +client.global.set("galaxy_for_star", response.body.id); +%} + + + +### +POST http://{{host}}/stars HTTP/1.1 +content-type: application/json + +{ + "name": "Sun", + "galaxy": { + "id": "{{galaxy_for_star}}" + } +} + +### \ No newline at end of file diff --git a/Tests/AppRestApiTests/Controllers/TodoController.http b/Tests/AppRestApiTests/Controllers/TodoController.http new file mode 100644 index 0000000..a8f624f --- /dev/null +++ b/Tests/AppRestApiTests/Controllers/TodoController.http @@ -0,0 +1,29 @@ +POST http://{{host}}/todos HTTP/1.1 +content-type: application/json +Authorization: Bearer {{userToken}} + +{ + "title": "todo_thing_001" +} + +> {% +client.test("Request executed successfully", function() { + client.assert(response.status === 200, "Response status is not 200"); +}); + +client.global.set("user_id_created", response.body.id); +%} + +### + + +GET http://{{host}}/todos HTTP/1.1 +Authorization: Bearer {{userToken}} + +### + + +DELETE http://{{host}}/todos/{{user_id_created}} HTTP/1.1 +Authorization: Bearer {{userToken}} + +### diff --git a/Tests/AppRestApiTests/Controllers/UserController.http b/Tests/AppRestApiTests/Controllers/UserController.http new file mode 100644 index 0000000..1ff0618 --- /dev/null +++ b/Tests/AppRestApiTests/Controllers/UserController.http @@ -0,0 +1,84 @@ +POST http://{{host}}/users HTTP/1.1 +content-type: application/json + +{ + "name": "Vapor", + "email": "test@vapor.codes", + "password": "secret", + "confirmPassword": "secret" +} + +### + +POST http://{{host}}/users HTTP/1.1 +content-type: application/json + +{ + "name": "Vapor_002", + "email": "test_002@vapor.codes", + "password": "secret", + "confirmPassword": "secret" +} + +### + +POST http://{{host}}/login HTTP/1.1 +# echo -n "test@vapor.codes:secret" | base64 +Authorization: Basic dGVzdEB2YXBvci5jb2RlczpzZWNyZXQ= + +> {% +client.test("Request executed successfully", function() { + client.assert(response.status === 200, "Response status is not 200"); + client.log("\n"); + client.log("userToken = " + response.body.value); + client.log("\n\n"); +}); + +client.global.set("userToken", response.body.value); +%} + +### + + +#POST http://{{host}}/login HTTP/1.1 +## echo -n "test_002@vapor.codes:secret" | base64 +#Authorization: Basic dGVzdF8wMDJAdmFwb3IuY29kZXM6c2VjcmV0 +# +#> {% +#client.test("Request executed successfully", function() { +# client.assert(response.status === 200, "Response status is not 200"); +# client.log("\n"); +# client.log("userToken = " + response.body.value); +# client.log("\n\n"); +#}); +# +#client.global.set("userToken", response.body.value); +#%} + +### + +GET http://{{host}}/me HTTP/1.1 +Authorization: Bearer {{userToken}} + +### + +POST http://{{host}}/login HTTP/1.1 +Authorization: Bearer {{userToken}} + +> {% +client.test("Request executed successfully", function() { + client.assert(response.status === 200, "Response status is not 200"); + client.log("\n"); + client.log("userToken = " + response.body.value); + client.log("\n\n"); +}); + +client.global.set("userToken", response.body.value); +%} + +### + +POST http://{{host}}/logout HTTP/1.1 +Authorization: Bearer {{userToken}} + +### diff --git a/Tests/AppRestApiTests/README.md b/Tests/AppRestApiTests/README.md new file mode 100644 index 0000000..0fb37b2 --- /dev/null +++ b/Tests/AppRestApiTests/README.md @@ -0,0 +1,34 @@ + + +## 术语表 + +- 环境 + + 名称 | 含义 + --- | ---- + dev | 开发环境 + uat | 测试环境 + pro | 正式环境 + + +## 文件 + +- rest-client.env.json + + 配置环境变量 + + +- rest-client.env.json + + 配置私密敏感的环境变量, 如: 用户名和密码 + + 不受版本控制, 请在 `.gitignore` 里忽略它. + + +## 使用教程 + +https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html#using-response-handler-scripts + +https://liqiang.io/post/http-debug-tools-with-jetbrains-712742e4 + +https://blog.csdn.net/BodyandSoul/article/details/103255866 \ No newline at end of file diff --git a/Tests/AppRestApiTests/http-request.http b/Tests/AppRestApiTests/http-request.http new file mode 100644 index 0000000..a85a6ee --- /dev/null +++ b/Tests/AppRestApiTests/http-request.http @@ -0,0 +1,23 @@ +GET http://{{host}} + +> {% +client.test("Baseline", function () { + client.assert(response.status == 200, '状态码为非200'); + client.log(response.headers) + client.log(response.body) + client.log(response.body.msg) +}); + +/** + * client.global.set 后,可以被后续请求通过{{demo}}获取该变量值,以实现更多连贯操作 + */ +client.global.set('demo', 'test'); +%} + +### + + + + + + diff --git a/Tests/AppRestApiTests/rest-client.env.json b/Tests/AppRestApiTests/rest-client.env.json new file mode 100644 index 0000000..e700167 --- /dev/null +++ b/Tests/AppRestApiTests/rest-client.env.json @@ -0,0 +1,11 @@ +{ + "dev": { + "host": "localhost:8080" + }, + "uat": { + "host": "localhost:8080" + }, + "prod": { + "host": "www.google.com" + } +} \ No newline at end of file diff --git a/Tests/AppRestApiTests/rest-client.private.env.json b/Tests/AppRestApiTests/rest-client.private.env.json new file mode 100644 index 0000000..63dace7 --- /dev/null +++ b/Tests/AppRestApiTests/rest-client.private.env.json @@ -0,0 +1,14 @@ +{ + "dev": { + "loginName": "***", + "password": "***" + }, + "uat": { + "loginName": "***", + "password": "***" + }, + "prod": { + "loginName": "***", + "password": "***" + } +} \ No newline at end of file diff --git a/Tests/AppRestApiTests/scripts/my-script.js b/Tests/AppRestApiTests/scripts/my-script.js new file mode 100644 index 0000000..e69de29 diff --git a/Tests/AppTests/AppTests.swift b/Tests/AppTests/AppTests.swift new file mode 100644 index 0000000..56f7a38 --- /dev/null +++ b/Tests/AppTests/AppTests.swift @@ -0,0 +1,19 @@ +@testable import App +import XCTVapor + +final class AppTests: XCTestCase { + func testHelloWorld() throws { + let app = Application(.testing) + defer { app.shutdown() } + try configure(app) + + let testEnv = try app.testable(method: .inMemory) + //let testEnv = try app.testable(method: .running) + //let testEnv = try app.testable(method: .running(port: 8123)) + + try testEnv.test(.GET, "hello") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "Hello, world!") + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..776fc01 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,74 @@ +# Docker Compose file for Vapor +# +# Install Docker on your system to run and test +# your Vapor app in a production-like environment. +# +# Note: This file is intended for testing and does not +# implement best practices for a production deployment. +# +# Learn more: https://docs.docker.com/compose/reference/ +# +# Build images: docker-compose build +# Start app: docker-compose up app +# Start database: docker-compose up db +# Run migrations: docker-compose up migrate +# Stop all: docker-compose down (add -v to wipe db) +# +version: '3.7' + +volumes: + db_data: + +x-shared_environment: &shared_environment + LOG_LEVEL: ${LOG_LEVEL:-debug} + DATABASE_HOST: db + DATABASE_NAME: vapor_database + DATABASE_USERNAME: vapor_username + DATABASE_PASSWORD: vapor_password + +services: + app: + image: demo_swift_server_vapor_auth:latest + build: + context: . + environment: + <<: *shared_environment + depends_on: + - db + ports: + - '8080:8080' +# user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user. + command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] + migrate: + image: demo_swift_server_vapor_auth:latest + build: + context: . + environment: + <<: *shared_environment + depends_on: + - db + command: ["migrate", "--yes"] + deploy: + replicas: 0 + revert: + image: demo_swift_server_vapor_auth:latest + build: + context: . + environment: + <<: *shared_environment + depends_on: + - db + command: ["migrate", "--revert", "--yes"] + deploy: + replicas: 0 + db: + image: postgres:12-alpine + volumes: + - db_data:/var/lib/postgresql/data/pgdata + environment: + PGDATA: /var/lib/postgresql/data/pgdata + POSTGRES_USER: vapor_username + POSTGRES_PASSWORD: vapor_password + POSTGRES_DB: vapor_database + ports: + - '5432:5432' diff --git a/etc/nginx/cert.key b/etc/nginx/cert.key new file mode 100644 index 0000000..be961bf --- /dev/null +++ b/etc/nginx/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJX2ojt17ii/1f +yn4PA4lNuFpiymdh9VCMbB1SjwyqrDYXzz6f/s92BSS62VW3rCjQROdmw3wOjTna +VWTnZXFwrZxOqP+5vBjDzz3wgzFX79oV97yJUvkGHB95vRHU5rlfgihtXZ8I8f94 +57Hr64UJCtjt4AGehg2RCemZJRE6YD+ZoHi1YapRAT9ifJiOFBuL/hV5rkFNrb1F +XfOR8dOBCF723uiynEZGarof6EliydgzUcL4JL+M6iQvlBQ5a7r4uAUfVDv7y6wJ +fD+5+2i3y4XbATDSuTgE5PRHAoBN3RGwkKbej+V+2u+jXQx3jIAWm8hTbWO9etX8 +Jg796csrAgMBAAECggEAcFubplgPSkqf0k3yrj/j4Yimbg7PRloRWniMl98XSzeR +axGIuM79RLWz+8l+SWB22VZI4w7Z7migShFxnqb8d8l2uHEVVrX7/21n+wXIh29d ++PnkqZ6xMdaJPWzgQTGHZyIBmpwtO1mDmRt2K9OaxCPYTEm7vY7AjRfBzmBfT3kc +CsVBTVXoEAaTsfyuKUVplpqO5NzIf24/pSYoOVqZoe+yV+qV9lb6/+OixL8EhnYW +fZLBL5MvjCgfEvm9mpSD3YSprhM+fMXvPd1tIcamZFu8RQLib+e6/FIgBgu44MgO +uXOCZulZPMM5ywidOCA2nGhMHdHct5NPzkbu6+864QKBgQD6fb4ozBbb04AL2aWg +kv7myYx5gAppuVzxM/jbSSQj+5/q1+/D2bu4k1TAhIj7wiyF1wpn7iTKIYmHHev2 +8TUurgwl6QY/BnLfdt0YByuu521DuVMcgxr+M+g8Ik9RXq+ruMJ9rCr4CGYqeyd+ +R/UlG4nybQGy5Kx0SJVL1pgzuwKBgQDNzSLsJu7nV/8mBNmgwfAaQt+Rb5l8PqtE +wLYl54q6yB+xG6FYKCtOYoI15UK+sA+lWdOj6mNuz86PbHO1f9AfW7b5UCjIG96c ++31edvZ0DrPuKH2hXZVmBr70lmU+UZFU9Zkxi8E7k9B/lLEFRas7jny2vQ2ExgQE +/C4a4Aj3UQKBgEXnK1cVRVfJWRV1gowiGXJsUoBhpAZHDVHekBcZ/TTxdnpCEo4U +SBfFPcSP9+5SDLy4+xVv1uJ1o3yKdBwwygeh2JAHzI9wDESiVibrcVxZBxv8Lt5p +E/JWp/uIE5yAkQOaBuStVDxtk51NSwMpr5bQbZmyo+O5fwrgapGEbGsbAoGAVKXn +k1KAOU+MdsrP6LtcUrDm3B8W9m+gamsJgJW4DhN6VZKTznrXNSp3fEuMJHquNMi5 +sFYcB9wZjgyBm0mzBPLoU6uQ2p379n3Klyw/OoVBpNeqtNCVPp3PtTKW+gb6zAMo +u6RMGoE15QP3u79oKtr0mQUxpkb8KEDBhfRYO8ECgYEArlJ68drPJkWG+yDBynP1 +mrM80t3mDH4uUNnCVp9M/YI4TaxEY2ClJyBMYOPdiWWlj/t4x0fwinm8DpsQZ83c +mKOVdyg9z0GA78rZXurwGCRZ46ZYvKbFKQoczxny0N2NQc4+1ucDM+UDdKrF5dZF +eBcuvSke9K9wAz5zF8IB8Yw= +-----END PRIVATE KEY----- diff --git a/etc/nginx/cert.pem b/etc/nginx/cert.pem new file mode 100644 index 0000000..ada266a --- /dev/null +++ b/etc/nginx/cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbjCCAlYCCQD/w0Nj8sG2HzANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJD +TjEQMA4GA1UECAwHQmVpSmluZzEQMA4GA1UEBwwHQmVpSmluZzEMMAoGA1UECgwD +R0NEMQwwCgYDVQQLDANHQ0QxDDAKBgNVBAMMA0dDRDEcMBoGCSqGSIb3DQEJARYN +R0NEQGdtYWlsLmNvbTAeFw0yMDA3MTYxMDM3NTVaFw0zMDA3MTQxMDM3NTVaMHkx +CzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlKaW5nMRAwDgYDVQQHDAdCZWlKaW5n +MQwwCgYDVQQKDANHQ0QxDDAKBgNVBAsMA0dDRDEMMAoGA1UEAwwDR0NEMRwwGgYJ +KoZIhvcNAQkBFg1HQ0RAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAyV9qI7de4ov9X8p+DwOJTbhaYspnYfVQjGwdUo8Mqqw2F88+n/7P +dgUkutlVt6wo0ETnZsN8Do052lVk52VxcK2cTqj/ubwYw8898IMxV+/aFfe8iVL5 +Bhwfeb0R1Oa5X4IobV2fCPH/eOex6+uFCQrY7eABnoYNkQnpmSUROmA/maB4tWGq +UQE/YnyYjhQbi/4Vea5BTa29RV3zkfHTgQhe9t7ospxGRmq6H+hJYsnYM1HC+CS/ +jOokL5QUOWu6+LgFH1Q7+8usCXw/uftot8uF2wEw0rk4BOT0RwKATd0RsJCm3o/l +ftrvo10Md4yAFpvIU21jvXrV/CYO/enLKwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQBNb48WpnG0pFqwETpXZgbycZZ8lPbQLvkGvhkQajYR9aDdMf75J4tGpHwPbDye +pR5oVfYDSjux2wLT5H0rCflFYr/aop5QOvUxQZHKeGmFLY+JiDw6PuW8+imm987t +ZNoNt2802FVrb8vCo64EDAw+OukhPGr6KM5iptntATmkGajXoKEKwB1of94reeS6 +i99VkULisaXDszpRaHvERN8W6BheTq1pjpY4A6t/4XNDOOjVs/idkV6Mc/xeKjDt +XkTP8VkfHTxzCPWZhB2rDrUJSz1E7Cf/5pSXoyMEpzCOup/w8ivcsz27AYxWEKCN +N+ZMFfly7iDg4JKlr5Jxs7Vl +-----END CERTIFICATE----- diff --git a/etc/nginx/fastcgi.conf b/etc/nginx/fastcgi.conf new file mode 100644 index 0000000..091738c --- /dev/null +++ b/etc/nginx/fastcgi.conf @@ -0,0 +1,26 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/etc/nginx/fastcgi.conf.default b/etc/nginx/fastcgi.conf.default new file mode 100644 index 0000000..091738c --- /dev/null +++ b/etc/nginx/fastcgi.conf.default @@ -0,0 +1,26 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/etc/nginx/fastcgi_params b/etc/nginx/fastcgi_params new file mode 100644 index 0000000..28decb9 --- /dev/null +++ b/etc/nginx/fastcgi_params @@ -0,0 +1,25 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/etc/nginx/fastcgi_params.default b/etc/nginx/fastcgi_params.default new file mode 100644 index 0000000..28decb9 --- /dev/null +++ b/etc/nginx/fastcgi_params.default @@ -0,0 +1,25 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/etc/nginx/koi-utf b/etc/nginx/koi-utf new file mode 100644 index 0000000..e7974ff --- /dev/null +++ b/etc/nginx/koi-utf @@ -0,0 +1,109 @@ + +# This map is not a full koi8-r <> utf8 map: it does not contain +# box-drawing and some other characters. Besides this map contains +# several koi8-u and Byelorussian letters which are not in koi8-r. +# If you need a full and standard map, use contrib/unicode2nginx/koi-utf +# map instead. + +charset_map koi8-r utf-8 { + + 80 E282AC ; # euro + + 95 E280A2 ; # bullet + + 9A C2A0 ; #   + + 9E C2B7 ; # · + + A3 D191 ; # small yo + A4 D194 ; # small Ukrainian ye + + A6 D196 ; # small Ukrainian i + A7 D197 ; # small Ukrainian yi + + AD D291 ; # small Ukrainian soft g + AE D19E ; # small Byelorussian short u + + B0 C2B0 ; # ° + + B3 D081 ; # capital YO + B4 D084 ; # capital Ukrainian YE + + B6 D086 ; # capital Ukrainian I + B7 D087 ; # capital Ukrainian YI + + B9 E28496 ; # numero sign + + BD D290 ; # capital Ukrainian soft G + BE D18E ; # capital Byelorussian short U + + BF C2A9 ; # (C) + + C0 D18E ; # small yu + C1 D0B0 ; # small a + C2 D0B1 ; # small b + C3 D186 ; # small ts + C4 D0B4 ; # small d + C5 D0B5 ; # small ye + C6 D184 ; # small f + C7 D0B3 ; # small g + C8 D185 ; # small kh + C9 D0B8 ; # small i + CA D0B9 ; # small j + CB D0BA ; # small k + CC D0BB ; # small l + CD D0BC ; # small m + CE D0BD ; # small n + CF D0BE ; # small o + + D0 D0BF ; # small p + D1 D18F ; # small ya + D2 D180 ; # small r + D3 D181 ; # small s + D4 D182 ; # small t + D5 D183 ; # small u + D6 D0B6 ; # small zh + D7 D0B2 ; # small v + D8 D18C ; # small soft sign + D9 D18B ; # small y + DA D0B7 ; # small z + DB D188 ; # small sh + DC D18D ; # small e + DD D189 ; # small shch + DE D187 ; # small ch + DF D18A ; # small hard sign + + E0 D0AE ; # capital YU + E1 D090 ; # capital A + E2 D091 ; # capital B + E3 D0A6 ; # capital TS + E4 D094 ; # capital D + E5 D095 ; # capital YE + E6 D0A4 ; # capital F + E7 D093 ; # capital G + E8 D0A5 ; # capital KH + E9 D098 ; # capital I + EA D099 ; # capital J + EB D09A ; # capital K + EC D09B ; # capital L + ED D09C ; # capital M + EE D09D ; # capital N + EF D09E ; # capital O + + F0 D09F ; # capital P + F1 D0AF ; # capital YA + F2 D0A0 ; # capital R + F3 D0A1 ; # capital S + F4 D0A2 ; # capital T + F5 D0A3 ; # capital U + F6 D096 ; # capital ZH + F7 D092 ; # capital V + F8 D0AC ; # capital soft sign + F9 D0AB ; # capital Y + FA D097 ; # capital Z + FB D0A8 ; # capital SH + FC D0AD ; # capital E + FD D0A9 ; # capital SHCH + FE D0A7 ; # capital CH + FF D0AA ; # capital hard sign +} diff --git a/etc/nginx/koi-win b/etc/nginx/koi-win new file mode 100644 index 0000000..72afabe --- /dev/null +++ b/etc/nginx/koi-win @@ -0,0 +1,103 @@ + +charset_map koi8-r windows-1251 { + + 80 88 ; # euro + + 95 95 ; # bullet + + 9A A0 ; #   + + 9E B7 ; # · + + A3 B8 ; # small yo + A4 BA ; # small Ukrainian ye + + A6 B3 ; # small Ukrainian i + A7 BF ; # small Ukrainian yi + + AD B4 ; # small Ukrainian soft g + AE A2 ; # small Byelorussian short u + + B0 B0 ; # ° + + B3 A8 ; # capital YO + B4 AA ; # capital Ukrainian YE + + B6 B2 ; # capital Ukrainian I + B7 AF ; # capital Ukrainian YI + + B9 B9 ; # numero sign + + BD A5 ; # capital Ukrainian soft G + BE A1 ; # capital Byelorussian short U + + BF A9 ; # (C) + + C0 FE ; # small yu + C1 E0 ; # small a + C2 E1 ; # small b + C3 F6 ; # small ts + C4 E4 ; # small d + C5 E5 ; # small ye + C6 F4 ; # small f + C7 E3 ; # small g + C8 F5 ; # small kh + C9 E8 ; # small i + CA E9 ; # small j + CB EA ; # small k + CC EB ; # small l + CD EC ; # small m + CE ED ; # small n + CF EE ; # small o + + D0 EF ; # small p + D1 FF ; # small ya + D2 F0 ; # small r + D3 F1 ; # small s + D4 F2 ; # small t + D5 F3 ; # small u + D6 E6 ; # small zh + D7 E2 ; # small v + D8 FC ; # small soft sign + D9 FB ; # small y + DA E7 ; # small z + DB F8 ; # small sh + DC FD ; # small e + DD F9 ; # small shch + DE F7 ; # small ch + DF FA ; # small hard sign + + E0 DE ; # capital YU + E1 C0 ; # capital A + E2 C1 ; # capital B + E3 D6 ; # capital TS + E4 C4 ; # capital D + E5 C5 ; # capital YE + E6 D4 ; # capital F + E7 C3 ; # capital G + E8 D5 ; # capital KH + E9 C8 ; # capital I + EA C9 ; # capital J + EB CA ; # capital K + EC CB ; # capital L + ED CC ; # capital M + EE CD ; # capital N + EF CE ; # capital O + + F0 CF ; # capital P + F1 DF ; # capital YA + F2 D0 ; # capital R + F3 D1 ; # capital S + F4 D2 ; # capital T + F5 D3 ; # capital U + F6 C6 ; # capital ZH + F7 C2 ; # capital V + F8 DC ; # capital soft sign + F9 DB ; # capital Y + FA C7 ; # capital Z + FB D8 ; # capital SH + FC DD ; # capital E + FD D9 ; # capital SHCH + FE D7 ; # capital CH + FF DA ; # capital hard sign +} diff --git a/etc/nginx/mime.types b/etc/nginx/mime.types new file mode 100644 index 0000000..2961256 --- /dev/null +++ b/etc/nginx/mime.types @@ -0,0 +1,97 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/etc/nginx/mime.types.default b/etc/nginx/mime.types.default new file mode 100644 index 0000000..2961256 --- /dev/null +++ b/etc/nginx/mime.types.default @@ -0,0 +1,97 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/etc/nginx/nginx.conf b/etc/nginx/nginx.conf new file mode 100644 index 0000000..a93e94a --- /dev/null +++ b/etc/nginx/nginx.conf @@ -0,0 +1,156 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 8080; + server_name localhost; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } + + + # another virtual host using mix of IP-, name-, and port-based configuration + # + #server { + # listen 8000; + # listen somename:8080; + # server_name somename alias another.alias; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + + # HTTPS server + # + #server { + # listen 443 ssl; + # server_name localhost; + + # ssl_certificate cert.pem; + # ssl_certificate_key cert.key; + + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 5m; + + # ssl_ciphers HIGH:!aNULL:!MD5; + # ssl_prefer_server_ciphers on; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + server { + listen 443 ssl; + server_name localhost; + + ssl_certificate cert.pem; + ssl_certificate_key cert.key; + + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 1d; + + #ssl_ciphers HIGH:!aNULL:!MD5; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; + ssl_prefer_server_ciphers on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_trusted_certificate cert.pem; + ssl_stapling on; + ssl_stapling_verify on; + add_header Strict-Transport-Security max-age=15768000; + + root ~/dev_kit/sdk/swift_source/readdle/demo_swift_server_vapor_auth/Public/; + + location / { + try_files $uri @proxy; + } + + location @proxy { + proxy_pass http://127.0.0.1:8080; + proxy_pass_header Server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_header Server; + proxy_connect_timeout 3s; + proxy_read_timeout 10s; + } + } + + include servers/*; +} diff --git a/etc/nginx/nginx.conf.default b/etc/nginx/nginx.conf.default new file mode 100644 index 0000000..f9ebe57 --- /dev/null +++ b/etc/nginx/nginx.conf.default @@ -0,0 +1,117 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 8080; + server_name localhost; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } + + + # another virtual host using mix of IP-, name-, and port-based configuration + # + #server { + # listen 8000; + # listen somename:8080; + # server_name somename alias another.alias; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + + # HTTPS server + # + #server { + # listen 443 ssl; + # server_name localhost; + + # ssl_certificate cert.pem; + # ssl_certificate_key cert.key; + + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 5m; + + # ssl_ciphers HIGH:!aNULL:!MD5; + # ssl_prefer_server_ciphers on; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + include servers/*; +} diff --git a/etc/nginx/scgi_params b/etc/nginx/scgi_params new file mode 100644 index 0000000..6d4ce4f --- /dev/null +++ b/etc/nginx/scgi_params @@ -0,0 +1,17 @@ + +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; +scgi_param REQUEST_SCHEME $scheme; +scgi_param HTTPS $https if_not_empty; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/etc/nginx/scgi_params.default b/etc/nginx/scgi_params.default new file mode 100644 index 0000000..6d4ce4f --- /dev/null +++ b/etc/nginx/scgi_params.default @@ -0,0 +1,17 @@ + +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; +scgi_param REQUEST_SCHEME $scheme; +scgi_param HTTPS $https if_not_empty; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/etc/nginx/uwsgi_params b/etc/nginx/uwsgi_params new file mode 100644 index 0000000..09c732c --- /dev/null +++ b/etc/nginx/uwsgi_params @@ -0,0 +1,17 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/etc/nginx/uwsgi_params.default b/etc/nginx/uwsgi_params.default new file mode 100644 index 0000000..09c732c --- /dev/null +++ b/etc/nginx/uwsgi_params.default @@ -0,0 +1,17 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/etc/nginx/win-utf b/etc/nginx/win-utf new file mode 100644 index 0000000..ed8bc00 --- /dev/null +++ b/etc/nginx/win-utf @@ -0,0 +1,126 @@ + +# This map is not a full windows-1251 <> utf8 map: it does not +# contain Serbian and Macedonian letters. If you need a full map, +# use contrib/unicode2nginx/win-utf map instead. + +charset_map windows-1251 utf-8 { + + 82 E2809A ; # single low-9 quotation mark + + 84 E2809E ; # double low-9 quotation mark + 85 E280A6 ; # ellipsis + 86 E280A0 ; # dagger + 87 E280A1 ; # double dagger + 88 E282AC ; # euro + 89 E280B0 ; # per mille + + 91 E28098 ; # left single quotation mark + 92 E28099 ; # right single quotation mark + 93 E2809C ; # left double quotation mark + 94 E2809D ; # right double quotation mark + 95 E280A2 ; # bullet + 96 E28093 ; # en dash + 97 E28094 ; # em dash + + 99 E284A2 ; # trade mark sign + + A0 C2A0 ; #   + A1 D18E ; # capital Byelorussian short U + A2 D19E ; # small Byelorussian short u + + A4 C2A4 ; # currency sign + A5 D290 ; # capital Ukrainian soft G + A6 C2A6 ; # borken bar + A7 C2A7 ; # section sign + A8 D081 ; # capital YO + A9 C2A9 ; # (C) + AA D084 ; # capital Ukrainian YE + AB C2AB ; # left-pointing double angle quotation mark + AC C2AC ; # not sign + AD C2AD ; # soft hypen + AE C2AE ; # (R) + AF D087 ; # capital Ukrainian YI + + B0 C2B0 ; # ° + B1 C2B1 ; # plus-minus sign + B2 D086 ; # capital Ukrainian I + B3 D196 ; # small Ukrainian i + B4 D291 ; # small Ukrainian soft g + B5 C2B5 ; # micro sign + B6 C2B6 ; # pilcrow sign + B7 C2B7 ; # · + B8 D191 ; # small yo + B9 E28496 ; # numero sign + BA D194 ; # small Ukrainian ye + BB C2BB ; # right-pointing double angle quotation mark + + BF D197 ; # small Ukrainian yi + + C0 D090 ; # capital A + C1 D091 ; # capital B + C2 D092 ; # capital V + C3 D093 ; # capital G + C4 D094 ; # capital D + C5 D095 ; # capital YE + C6 D096 ; # capital ZH + C7 D097 ; # capital Z + C8 D098 ; # capital I + C9 D099 ; # capital J + CA D09A ; # capital K + CB D09B ; # capital L + CC D09C ; # capital M + CD D09D ; # capital N + CE D09E ; # capital O + CF D09F ; # capital P + + D0 D0A0 ; # capital R + D1 D0A1 ; # capital S + D2 D0A2 ; # capital T + D3 D0A3 ; # capital U + D4 D0A4 ; # capital F + D5 D0A5 ; # capital KH + D6 D0A6 ; # capital TS + D7 D0A7 ; # capital CH + D8 D0A8 ; # capital SH + D9 D0A9 ; # capital SHCH + DA D0AA ; # capital hard sign + DB D0AB ; # capital Y + DC D0AC ; # capital soft sign + DD D0AD ; # capital E + DE D0AE ; # capital YU + DF D0AF ; # capital YA + + E0 D0B0 ; # small a + E1 D0B1 ; # small b + E2 D0B2 ; # small v + E3 D0B3 ; # small g + E4 D0B4 ; # small d + E5 D0B5 ; # small ye + E6 D0B6 ; # small zh + E7 D0B7 ; # small z + E8 D0B8 ; # small i + E9 D0B9 ; # small j + EA D0BA ; # small k + EB D0BB ; # small l + EC D0BC ; # small m + ED D0BD ; # small n + EE D0BE ; # small o + EF D0BF ; # small p + + F0 D180 ; # small r + F1 D181 ; # small s + F2 D182 ; # small t + F3 D183 ; # small u + F4 D184 ; # small f + F5 D185 ; # small kh + F6 D186 ; # small ts + F7 D187 ; # small ch + F8 D188 ; # small sh + F9 D189 ; # small shch + FA D18A ; # small hard sign + FB D18B ; # small y + FC D18C ; # small soft sign + FD D18D ; # small e + FE D18E ; # small yu + FF D18F ; # small ya +}