Skip to content

Commit

Permalink
Merge branch 'main' into 2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Joannis authored Sep 12, 2024
2 parents 4b8c221 + b91c655 commit 5fb6f04
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 114 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Examples converted to Hummingbird 2.0
- [jobs](https://github.com/hummingbird-project/hummingbird-examples/tree/main/jobs) - Demonstrating offloading of jobs to another server.
- [multipart-form](https://github.com/hummingbird-project/hummingbird-examples/tree/main/multipart-form) - HTML form using Multipart form data, using MultipartKit
- [proxy-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/proxy-server) - Using AsyncHTTPClient to build a proxy server
- [response-body-processing](https://github.com/hummingbird-project/hummingbird-examples/tree/main/proxy-server) - Example showing how to process a response body in middleware.
- [sessions](https://github.com/hummingbird-project/hummingbird-examples/tree/main/sessions) - Username/password and session authentication.
- [todos-dynamodb](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-dynamodb) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB.
- [todos-lambda](https://github.com/hummingbird-project/hummingbird-examples/tree/main/todos-lambda) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB and running on AWS Lambda.
Expand Down
2 changes: 2 additions & 0 deletions todos-postgres-tutorial/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.build
.git
11 changes: 5 additions & 6 deletions todos-postgres-tutorial/.gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
.DS_Store
/.build
/.swiftpm
/.devContainer
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.vscode
Package.resolved
/.vscode/*
!/.vscode/hummingbird.code-snippets
86 changes: 86 additions & 0 deletions todos-postgres-tutorial/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# ================================
# Build image
# ================================
FROM swift:5.10-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 \
&& rm -rf /var/lib/apt/lists/*

# 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

# Copy entire repo into container
COPY . .

# Build everything, with optimizations, with static linking, and using jemalloc
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)/Todos" ./

# 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 resouces 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 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 hummingbird user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app hummingbird

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=hummingbird:hummingbird /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 hummingbird user
USER hummingbird:hummingbird

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Hummingbird service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./Todos"]
CMD ["--hostname", "0.0.0.0", "--port", "8080"]
27 changes: 12 additions & 15 deletions todos-postgres-tutorial/Package.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// swift-tools-version: 5.9
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "todos-postgres-tutorial",
platforms: [
.macOS(.v14),
platforms: [.macOS(.v14), .iOS(.v17), .tvOS(.v17)],
products: [
.executable(name: "App", targets: ["App"]),
],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
Expand All @@ -13,27 +16,21 @@ let package = Package(
],
targets: [
.executableTarget(
name: "Todos",
name: "App",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Hummingbird", package: "hummingbird"),
.product(name: "PostgresNIO", package: "postgres-nio"),
.product(name: "_ConnectionPoolModule", package: "postgres-nio"),
],
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 <https://github.com/swift-server/guides#building-for-production> for details.
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)),
]
path: "Sources/App"
),
.testTarget(
name: "TodosTests",
name: "AppTests",
dependencies: [
"Todos",
.product(name: "Hummingbird", package: "hummingbird"),
.byName(name: "App"),
.product(name: "HummingbirdTesting", package: "hummingbird"),
]
],
path: "Tests/AppTests"
),
]
)
16 changes: 2 additions & 14 deletions todos-postgres-tutorial/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,2 @@
# Todos Postgres Tutorial

This is the sample code that goes along with the Postgres Todos [tutorial](https://hummingbird-project.github.io/hummingbird-docs/2.0/tutorials/todos) in the documentation. The application has six routes

- GET /todos: Lists all the todos in the database
- POST /todos: Creates a new todo
- DELETE /todos: Deletes all the todos
- GET /todos/:id : Returns a single todo with id
- PATCH /todos/:id : Edits todo with id
- DELETE /todos/:id : Deletes todo with id

A todo consists of a title, order number, url to link to edit/get/delete that todo and whether that todo is complete or not.

The example requires a postgres database running locally. Follow [instructions](https://hummingbird-project.github.io/hummingbird-docs/2.0/tutorials/hummingbird/todos-4-postgres) in the tutorial to set this up.
# todos-postgres-tutorial
Hummingbird server framework project
30 changes: 30 additions & 0 deletions todos-postgres-tutorial/Sources/App/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ArgumentParser
import Hummingbird
import Logging

@main
struct App: AsyncParsableCommand, AppArguments {
@Option(name: .shortAndLong)
var hostname: String = "127.0.0.1"

@Option(name: .shortAndLong)
var port: Int = 8080

@Option(name: .shortAndLong)
var logLevel: Logger.Level?

@Flag
var inMemoryTesting: Bool = false

func run() async throws {
let app = try await buildApplication(self)
try await app.runService()
}
}

/// Extend `Logger.Level` so it can be used as an argument
#if hasFeature(RetroactiveAttribute)
extension Logger.Level: @retroactive ExpressibleByArgument {}
#else
extension Logger.Level: ExpressibleByArgument {}
#endif
77 changes: 77 additions & 0 deletions todos-postgres-tutorial/Sources/App/Application+build.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Hummingbird
import Logging
import PostgresNIO

/// Application arguments protocol. We use a protocol so we can call
/// `buildApplication` inside Tests as well as in the App executable.
/// Any variables added here also have to be added to `App` in App.swift and
/// `TestArguments` in AppTest.swift
public protocol AppArguments {
var hostname: String { get }
var port: Int { get }
var logLevel: Logger.Level? { get }
var inMemoryTesting: Bool { get }
}

// Request context used by application
typealias AppRequestContext = BasicRequestContext

/// Build application
/// - Parameter arguments: application arguments
public func buildApplication(_ arguments: some AppArguments) async throws -> some ApplicationProtocol {
let environment = Environment()
let logger = {
var logger = Logger(label: "todos-postgres-tutorial")
logger.logLevel =
arguments.logLevel ??
environment.get("LOG_LEVEL").map { Logger.Level(rawValue: $0) ?? .info } ??
.info
return logger
}()
var postgresRepository: TodoPostgresRepository?
let router: Router<AppRequestContext>
if !arguments.inMemoryTesting {
let client = PostgresClient(
configuration: .init(host: "localhost", username: "todos", password: "todos", database: "hummingbird", tls: .disable),
backgroundLogger: logger
)
let repository = TodoPostgresRepository(client: client, logger: logger)
postgresRepository = repository
router = buildRouter(repository)
} else {
router = buildRouter(TodoMemoryRepository())
}
var app = Application(
router: router,
configuration: .init(
address: .hostname(arguments.hostname, port: arguments.port),
serverName: "todos-postgres-tutorial"
),
logger: logger
)
// if we setup a postgres service then add as a service and run createTable before
// server starts
if let postgresRepository {
app.addServices(postgresRepository.client)
app.beforeServerStarts {
try await postgresRepository.createTable()
}
}
return app
}

/// Build router
func buildRouter(_ repository: some TodoRepository) -> Router<AppRequestContext> {
let router = Router(context: AppRequestContext.self)
// Add middleware
router.addMiddleware {
// logging middleware
LogRequestsMiddleware(.info)
}
// Add health endpoint
router.get("/health") { _, _ -> HTTPResponse.Status in
return .ok
}
router.addRoutes(TodoController(repository: repository).endpoints, atPath: "/todos")
return router
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ struct TodoController<Repository: TodoRepository> {
// Todo repository
let repository: Repository

// add Todos API to router group
func addRoutes(to group: RouterGroup<some RequestContext>) {
group
// return todo endpoints
var endpoints: RouteCollection<AppRequestContext> {
return RouteCollection(context: AppRequestContext.self)
.get(":id", use: self.get)
.get(use: self.list)
.post(use: self.create)
Expand Down
File renamed without changes.
73 changes: 0 additions & 73 deletions todos-postgres-tutorial/Sources/Todos.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import Foundation
import Hummingbird
import HummingbirdTesting
@testable import Todos
import Logging
import XCTest

final class TodosTests: XCTestCase {
@testable import App

final class AppTests: XCTestCase {
struct TestArguments: AppArguments {
let hostname = "127.0.0.1"
let port = 8080
let logLevel: Logger.Level? = nil
let inMemoryTesting = true
}

Expand Down

0 comments on commit 5fb6f04

Please sign in to comment.