commit 97db0f39c1674c7ecda98fe5643abe73879b4a60
Author: rpm-mcarman <65228808+rpm-mcarman@users.noreply.github.com>
Date: Wed Jan 24 22:54:09 2024 -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..920fa82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+Packages
+.build
+xcuserdata
+*.xcodeproj
+DerivedData/
+.DS_Store
+db.sqlite
+.swiftpm
+.env
+.env.*
+! .env.example
+.vscode
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7e7ad11
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,88 @@
+# ================================
+# Build image
+# ================================
+FROM swift:5.9-jammy as build
+
+# Install OS updates
+RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
+ && apt-get -q update \
+ && apt-get -q dist-upgrade -y \
+ && apt-get install -y libjemalloc-dev
+
+# Set up a build area
+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 --skip-update \
+ $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)
+
+# Copy entire repo into container
+COPY . .
+
+# Build everything, with optimizations, with static linking, and using jemalloc
+# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
+RUN swift build -c release \
+ --static-swift-stdlib \
+ -Xlinker -ljemalloc
+
+# Switch to the staging area
+WORKDIR /staging
+
+# Copy main executable to staging area
+RUN cp "$(swift build --package-path /build -c release --show-bin-path)/App" ./
+
+# Copy static swift backtracer binary to staging area
+RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./
+
+# Copy resources bundled by SPM to staging area
+RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;
+
+# Copy any resources from the public directory and views directory if the directories exist
+# Ensure that by default, neither the directory nor any of its contents are writable.
+RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
+RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true
+
+# ================================
+# Run image
+# ================================
+FROM ubuntu:jammy
+
+# Make sure all system packages are up to date, and install only essential packages.
+RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
+ && apt-get -q update \
+ && apt-get -q dist-upgrade -y \
+ && apt-get -q install -y \
+ libjemalloc2 \
+ ca-certificates \
+ tzdata \
+# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
+ # libcurl4 \
+# If your app or its dependencies import FoundationXML, also install `libxml2`.
+ # libxml2 \
+ && rm -r /var/lib/apt/lists/*
+
+# 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 built executable and any staged resources from builder
+COPY --from=build --chown=vapor:vapor /staging /app
+
+# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
+ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static
+
+# Ensure all further commands run as the vapor user
+USER vapor:vapor
+
+# Let Docker bind to port 8080
+EXPOSE 8080
+
+# Start the Vapor service when the image is run, default to listening on 8080 in production environment
+ENTRYPOINT ["./App"]
+CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..a89fa08
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,36 @@
+// swift-tools-version:5.9
+import PackageDescription
+
+let package = Package(
+ name: "cm_services",
+ platforms: [
+ .macOS(.v13)
+ ],
+ dependencies: [
+ // 💧 A server-side Swift web framework.
+ .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0"),
+ // 🗄 An ORM for SQL and NoSQL databases.
+ .package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"),
+ // 🐘 Fluent driver for Postgres.
+ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"),
+ ],
+ targets: [
+ .executableTarget(
+ name: "App",
+ dependencies: [
+ .product(name: "Fluent", package: "fluent"),
+ .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
+ .product(name: "Vapor", package: "vapor"),
+ ]
+ ),
+ .testTarget(name: "AppTests", dependencies: [
+ .target(name: "App"),
+ .product(name: "XCTVapor", package: "vapor"),
+
+ // Workaround for https://github.com/apple/swift-package-manager/issues/6940
+ .product(name: "Vapor", package: "vapor"),
+ .product(name: "Fluent", package: "Fluent"),
+ .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
+ ])
+ ]
+)
diff --git a/Public/.gitkeep b/Public/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/Resources/Views/index.leaf b/Resources/Views/index.leaf
new file mode 100644
index 0000000..10cb4f7
--- /dev/null
+++ b/Resources/Views/index.leaf
@@ -0,0 +1,12 @@
+
+
+
+
+
+ #(title)
+
+
+
+ #(title)
+
+
\ 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..265228d
--- /dev/null
+++ b/Sources/App/Controllers/TodoController.swift
@@ -0,0 +1,31 @@
+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) async throws -> [Todo] {
+ try await Todo.query(on: req.db).all()
+ }
+
+ func create(req: Request) async throws -> Todo {
+ let todo = try req.content.decode(Todo.self)
+ try await todo.save(on: req.db)
+ return todo
+ }
+
+ func delete(req: Request) async throws -> HTTPStatus {
+ guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else {
+ throw Abort(.notFound)
+ }
+ try await todo.delete(on: req.db)
+ return .noContent
+ }
+}
diff --git a/Sources/App/Migrations/CreateTodo.swift b/Sources/App/Migrations/CreateTodo.swift
new file mode 100644
index 0000000..bf6d945
--- /dev/null
+++ b/Sources/App/Migrations/CreateTodo.swift
@@ -0,0 +1,14 @@
+import Fluent
+
+struct CreateTodo: AsyncMigration {
+ func prepare(on database: Database) async throws {
+ try await database.schema("todos")
+ .id()
+ .field("title", .string, .required)
+ .create()
+ }
+
+ func revert(on database: Database) async throws {
+ try await 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..3b43411
--- /dev/null
+++ b/Sources/App/configure.swift
@@ -0,0 +1,24 @@
+import NIOSSL
+import Fluent
+import FluentPostgresDriver
+import Vapor
+
+// configures your application
+public func configure(_ app: Application) async throws {
+ // uncomment to serve files from /Public folder
+ // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
+
+ app.databases.use(DatabaseConfigurationFactory.postgres(configuration: .init(
+ hostname: Environment.get("DATABASE_HOST") ?? "localhost",
+ port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber,
+ username: Environment.get("DATABASE_USERNAME") ?? "vapor_username",
+ password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password",
+ database: Environment.get("DATABASE_NAME") ?? "vapor_database",
+ tls: .prefer(try .init(configuration: .clientDefault)))
+ ), as: .psql)
+
+ app.migrations.add(CreateTodo())
+
+ // register routes
+ try routes(app)
+}
diff --git a/Sources/App/entrypoint.swift b/Sources/App/entrypoint.swift
new file mode 100644
index 0000000..e875aee
--- /dev/null
+++ b/Sources/App/entrypoint.swift
@@ -0,0 +1,21 @@
+import Vapor
+import Logging
+
+@main
+enum Entrypoint {
+ static func main() async throws {
+ var env = try Environment.detect()
+ try LoggingSystem.bootstrap(from: &env)
+
+ let app = Application(env)
+ defer { app.shutdown() }
+
+ do {
+ try await configure(app)
+ } catch {
+ app.logger.report(error: error)
+ throw error
+ }
+ try await app.execute()
+ }
+}
diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift
new file mode 100644
index 0000000..f958df1
--- /dev/null
+++ b/Sources/App/routes.swift
@@ -0,0 +1,14 @@
+import Fluent
+import Vapor
+
+func routes(_ app: Application) throws {
+ app.get { req async in
+ "It works!"
+ }
+
+ app.get("hello") { req async -> String in
+ "Hello, world!"
+ }
+
+ try app.register(collection: TodoController())
+}
diff --git a/Tests/AppTests/AppTests.swift b/Tests/AppTests/AppTests.swift
new file mode 100644
index 0000000..8cfd310
--- /dev/null
+++ b/Tests/AppTests/AppTests.swift
@@ -0,0 +1,15 @@
+@testable import App
+import XCTVapor
+
+final class AppTests: XCTestCase {
+ func testHelloWorld() async throws {
+ let app = Application(.testing)
+ defer { app.shutdown() }
+ try await configure(app)
+
+ try app.test(.GET, "hello", afterResponse: { 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..e5e331c
--- /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 run 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: :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: :latest
+ build:
+ context: .
+ environment:
+ <<: *shared_environment
+ depends_on:
+ - db
+ command: ["migrate", "--yes"]
+ deploy:
+ replicas: 0
+ revert:
+ image: :latest
+ build:
+ context: .
+ environment:
+ <<: *shared_environment
+ depends_on:
+ - db
+ command: ["migrate", "--revert", "--yes"]
+ deploy:
+ replicas: 0
+ db:
+ image: postgres:16-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'