commit ba9bfd5e6ac6bb0695c3d1a85a573e309abf3be4 Author: Michael Simard Date: Mon May 25 20:58:43 2020 -0500 first commit 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/.gitignore b/.gitignore new file mode 100644 index 0000000..5b7445c --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +Packages +.build +xcuserdata +*.xcodeproj +DerivedData/ +.DS_Store +db.sqlite +.swiftpm + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c94d2ec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# ================================ +# 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 --home-dir /app vapor + +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 + +# 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"] \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..d9797cb --- /dev/null +++ b/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "cod-backend", + platforms: [ + .macOS(.v10_15) + ], + dependencies: [ + // 💧 A server-side Swift web framework. + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"), + .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"), + .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc") + ], + targets: [ + .target( + name: "App", + dependencies: [ + .product(name: "Fluent", package: "fluent"), + .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), + .product(name: "Vapor", package: "vapor") + ], + 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"), + ]) + ] +) \ 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/TodoController.swift b/Sources/App/Controllers/TodoController.swift new file mode 100644 index 0000000..7096a76 --- /dev/null +++ b/Sources/App/Controllers/TodoController.swift @@ -0,0 +1,29 @@ +import Fluent +import Vapor + +struct TodoController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let todos = 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/Migrations/CreateTodo.swift b/Sources/App/Migrations/CreateTodo.swift new file mode 100644 index 0000000..f06a4cd --- /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("todos") + .id() + .field("title", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + return database.schema("todos").delete() + } +} diff --git a/Sources/App/Models/Todo.swift b/Sources/App/Models/Todo.swift new file mode 100644 index 0000000..c3c9eac --- /dev/null +++ b/Sources/App/Models/Todo.swift @@ -0,0 +1,19 @@ +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/configure.swift b/Sources/App/configure.swift new file mode 100644 index 0000000..aa3f805 --- /dev/null +++ b/Sources/App/configure.swift @@ -0,0 +1,21 @@ +import Fluent +import FluentPostgresDriver +import Vapor + +// configures your application +public func configure(_ app: Application) throws { + // uncomment to serve files from /Public folder + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + app.databases.use(.postgres( + hostname: Environment.get("DATABASE_HOST") ?? "localhost", + username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", + password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", + database: Environment.get("DATABASE_NAME") ?? "vapor_database" + ), as: .psql) + + app.migrations.add(CreateTodo()) + + // register routes + try routes(app) +} \ No newline at end of file diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift new file mode 100644 index 0000000..0871197 --- /dev/null +++ b/Sources/App/routes.swift @@ -0,0 +1,14 @@ +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: TodoController()) +} \ No newline at end of file diff --git a/Sources/Run/main.swift b/Sources/Run/main.swift new file mode 100644 index 0000000..373be5f --- /dev/null +++ b/Sources/Run/main.swift @@ -0,0 +1,9 @@ +import App +import Vapor + +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/AppTests/AppTests.swift b/Tests/AppTests/AppTests.swift new file mode 100644 index 0000000..2a1cdb1 --- /dev/null +++ b/Tests/AppTests/AppTests.swift @@ -0,0 +1,15 @@ +@testable import App +import XCTVapor + +final class AppTests: XCTestCase { + func testHelloWorld() throws { + let app = Application(.testing) + defer { app.shutdown() } + try configure(app) + + try app.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..2037718 --- /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: cod-backend: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: cod-backend:latest + build: + context: . + environment: + <<: *shared_environment + depends_on: + - db + command: ["migrate", "--yes"] + deploy: + replicas: 0 + revert: + image: cod-backend: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' \ No newline at end of file