├── 1 ├── Package.swift └── Sources │ └── App │ └── main.swift ├── 2 ├── Package.swift ├── Sources │ └── App │ │ ├── Migrations │ │ └── 1_CreatePost.swift │ │ ├── Models │ │ └── Post.swift │ │ ├── configure.swift │ │ └── main.swift ├── Tests │ └── AppTests │ │ └── PostTests.swift └── docker-compose.yml ├── 3 ├── .env.development ├── .env.testing ├── Package.swift ├── Sources │ └── App │ │ ├── Migrations │ │ └── 1_CreatePost.swift │ │ ├── Models │ │ └── Post.swift │ │ ├── configure.swift │ │ └── main.swift ├── Tests │ └── AppTests │ │ └── PostTests.swift └── docker-compose.yml ├── 4 ├── .env.development ├── .env.testing ├── Package.swift ├── Sources │ └── App │ │ ├── Controllers │ │ └── PostController.swift │ │ ├── Migrations │ │ └── 1_CreatePost.swift │ │ ├── Models │ │ └── Post.swift │ │ ├── configure.swift │ │ └── main.swift ├── Tests │ └── AppTests │ │ └── PostTests.swift └── docker-compose.yml ├── 5 ├── .env.development ├── .env.testing ├── Package.swift ├── Sources │ └── App │ │ ├── Controllers │ │ ├── PostController.swift │ │ └── UserController.swift │ │ ├── Migrations │ │ ├── 1_CreatePost.swift │ │ └── 2_CreateUser.swift │ │ ├── Models │ │ ├── Post.swift │ │ └── User.swift │ │ ├── configure.swift │ │ └── main.swift ├── Tests │ └── AppTests │ │ ├── PostTests.swift │ │ └── UserTests.swift └── docker-compose.yml ├── 6 ├── .env.development ├── .env.testing ├── Package.swift ├── Sources │ └── App │ │ ├── Controllers │ │ ├── PostController.swift │ │ └── UserController.swift │ │ ├── Migrations │ │ ├── 1_CreatePost.swift │ │ ├── 2_CreateUser.swift │ │ └── 3_AddUserIDToPost.swift │ │ ├── Models │ │ ├── Post.swift │ │ └── User.swift │ │ ├── configure.swift │ │ └── main.swift ├── Tests │ └── AppTests │ │ ├── PostTests.swift │ │ └── UserTests.swift └── docker-compose.yml ├── .gitignore └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | Package.resolved 4 | .swiftpm -------------------------------------------------------------------------------- /1/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HelloWorld", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | ], 12 | targets: [ 13 | .executableTarget( 14 | name: "App", 15 | dependencies: [ 16 | .product(name: "Vapor", package: "vapor") 17 | ] 18 | ), 19 | ] 20 | ) -------------------------------------------------------------------------------- /1/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | let app = Application() 4 | 5 | app.http.server.configuration.port = 8080 6 | 7 | defer { app.shutdown() } 8 | 9 | app.get { req async in 10 | "It works!" 11 | } 12 | 13 | try app.run() -------------------------------------------------------------------------------- /2/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MicroBlog", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), 12 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | ] 22 | ), 23 | .testTarget(name: "AppTests", dependencies: [ 24 | .target(name: "App"), 25 | .product(name: "XCTVapor", package: "vapor"), 26 | ]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /2/Sources/App/Migrations/1_CreatePost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | // 定义 CreatePost 结构体,实现 AsyncMigration 协议 4 | struct CreatePost: AsyncMigration { 5 | // 准备方法,在数据库上进行准备操作 6 | func prepare(on database: Database) async throws { 7 | // 创建 Post 表的数据库模式对象 8 | try await database.schema(Post.schema) 9 | .id() // 添加 id 列 10 | .field("content", .string, .required) // 添加 content 列,类型为字符串,不能为空 11 | .field("created_at", .datetime) // 添加 created_at 列,类型为日期时间 12 | .create() // 创建 Post 表 13 | } 14 | 15 | // 回滚方法,在数据库上进行回滚操作 16 | func revert(on database: Database) async throws { 17 | try await database.schema(Post.schema).delete() // 删除 Post 表的数据库模式对象 18 | } 19 | } -------------------------------------------------------------------------------- /2/Sources/App/Models/Post.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Post: Model { 5 | // 数据库中的表名 6 | static let schema = "posts" 7 | 8 | // 唯一性标识符 9 | @ID(key: .id) 10 | var id: UUID? 11 | 12 | // 内容 13 | @Field(key: "content") 14 | var content: String 15 | 16 | // 创建时间 17 | @Timestamp(key: "created_at", on: .create) 18 | var createdAt: Date? 19 | 20 | init() { } 21 | 22 | init(id: UUID? = nil, content: String) { 23 | self.id = id 24 | self.content = content 25 | } 26 | } -------------------------------------------------------------------------------- /2/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | public func configure(_ app: Application) throws { 6 | app.databases.use(.postgres(configuration: SQLPostgresConfiguration( 7 | hostname: "localhost", 8 | port: 5432, 9 | username: "vapor_username", 10 | password: "vapor_password", 11 | database: "vapor_database", 12 | tls: .prefer(try .init(configuration: .clientDefault))) 13 | ), as: .psql) 14 | 15 | app.migrations.add([CreatePost()]) 16 | } 17 | -------------------------------------------------------------------------------- /2/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | import FluentPostgresDriver 4 | 5 | let app = Application() 6 | 7 | app.http.server.configuration.port = 8080 8 | 9 | defer { app.shutdown() } 10 | 11 | app.get { req async in 12 | "It works!" 13 | } 14 | 15 | try configure(app) 16 | 17 | try app.run() 18 | -------------------------------------------------------------------------------- /2/Tests/AppTests/PostTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class PostTests: XCTestCase { 5 | func testCreatePost() async throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | 9 | try configure(app) 10 | 11 | // autoRevert 将自动执行所有 Migration 中 revert 的内容 12 | try await app.autoRevert() 13 | // autoMigrate 将自动执行所有 Migration 中 prepare 的内容 14 | // 这两步将重建我们的数据库,为我们提供一个干净的测试环境 15 | try await app.autoMigrate() 16 | 17 | let post = Post(content: "Hello, world!") 18 | 19 | try await post.save(on: app.db) 20 | 21 | let postID = try? post.requireID() 22 | // 如果 postID 不为 nil 则成功创建,测试通过 23 | XCTAssertNotNil(postID) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' # 定义 Docker Compose 文件的版本,此处使用的是版本 3.7 2 | 3 | volumes: # 定义卷部分 4 | db_data: # docker 会使用这个键作为名字,自动创建 db_data 卷来存储数据 5 | 6 | services: # 定义服务部分 7 | 8 | db: # db 服务配置 9 | image: 'postgres:15-alpine' # 使用 PostgreSQL 15 Alpine 版本的镜像 10 | volumes: # 定义挂载卷 11 | - 'db_data:/var/lib/postgresql/data/pgdata' # 将 db_data 卷挂载到容器的 /var/lib/postgresql/data/pgdata 目录 12 | environment: # 定义环境变量 13 | PGDATA: '/var/lib/postgresql/data/pgdata' # 设置 PGDATA 环境变量为 /var/lib/postgresql/data/pgdata 14 | POSTGRES_USER: 'vapor_username' # 设置 POSTGRES_USER 环境变量为 vapor_username 15 | POSTGRES_PASSWORD: 'vapor_password' # 设置 POSTGRES_PASSWORD 环境变量为 vapor_password 16 | POSTGRES_DB: 'vapor_database' # 设置 POSTGRES_DB 环境变量为 vapor_database 17 | ports: # 定义端口映射,将主机的 5432 端口映射到容器的 5432 端口 18 | - '5432:5432' -------------------------------------------------------------------------------- /3/.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5432 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /3/.env.testing: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5442 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /3/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MicroBlog", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), 12 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | ] 22 | ), 23 | .testTarget(name: "AppTests", dependencies: [ 24 | .target(name: "App"), 25 | .product(name: "XCTVapor", package: "vapor"), 26 | ]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /3/Sources/App/Migrations/1_CreatePost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | // 定义 CreatePost 结构体,实现 AsyncMigration 协议 4 | struct CreatePost: AsyncMigration { 5 | // 准备方法,在数据库上进行准备操作 6 | func prepare(on database: Database) async throws { 7 | // 创建 Post 表的数据库模式对象 8 | try await database.schema(Post.schema) 9 | .id() // 添加 id 列 10 | .field("content", .string, .required) // 添加 content 列,类型为字符串,不能为空 11 | .field("created_at", .datetime) // 添加 created_at 列,类型为日期时间 12 | .create() // 创建 Post 表 13 | } 14 | 15 | // 回滚方法,在数据库上进行回滚操作 16 | func revert(on database: Database) async throws { 17 | try await database.schema(Post.schema).delete() // 删除 Post 表的数据库模式对象 18 | } 19 | } -------------------------------------------------------------------------------- /3/Sources/App/Models/Post.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Post: Model, Content { 5 | // 数据库中的表名 6 | static let schema = "posts" 7 | 8 | // 唯一性标识符 9 | @ID(key: .id) 10 | var id: UUID? 11 | 12 | // 内容 13 | @Field(key: "content") 14 | var content: String 15 | 16 | // 创建时间 17 | @Timestamp(key: "created_at", on: .create) 18 | var createdAt: Date? 19 | 20 | init() { } 21 | 22 | init(id: UUID? = nil, content: String) { 23 | self.id = id 24 | self.content = content 25 | } 26 | } 27 | 28 | extension Post { 29 | struct CreateDTO: Content { 30 | let content: String 31 | } 32 | } -------------------------------------------------------------------------------- /3/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | public func configure(_ app: Application) throws { 6 | app.databases.use(.postgres(configuration: SQLPostgresConfiguration( 7 | hostname: Environment.get("DATABASE_HOST") ?? "localhost", 8 | port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, 9 | username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", 10 | password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", 11 | database: Environment.get("DATABASE_NAME") ?? "vapor_database", 12 | tls: .prefer(try .init(configuration: .clientDefault))) 13 | ), as: .psql) 14 | 15 | app.migrations.add([CreatePost()]) 16 | 17 | app.post("posts") { req async throws -> Post in 18 | let postData = try req.content.decode(Post.CreateDTO.self) 19 | 20 | let post = Post(content: postData.content) 21 | 22 | try await post.create(on: req.db) 23 | 24 | return post 25 | } 26 | 27 | app.get("posts") { req async throws -> [Post] in 28 | let posts = try await Post.query(on: req.db).all() 29 | 30 | return posts 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /3/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | import FluentPostgresDriver 4 | 5 | let app = Application(.development) 6 | 7 | app.http.server.configuration.port = 8080 8 | 9 | defer { app.shutdown() } 10 | 11 | app.get { req async in 12 | "It works!" 13 | } 14 | 15 | try configure(app) 16 | 17 | try app.run() 18 | -------------------------------------------------------------------------------- /3/Tests/AppTests/PostTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class PostTests: XCTestCase { 5 | func testCreatePost() async throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | 14 | let postDTO = Post.CreateDTO(content: "Post created from test") 15 | 16 | try app.test(.POST, "posts", beforeRequest: { req in 17 | try req.content.encode(postDTO) 18 | }, afterResponse: { res in 19 | XCTAssertEqual(res.status, .ok) 20 | 21 | let post = try res.content.decode(Post.self) 22 | 23 | XCTAssertEqual(postDTO.content, post.content) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /3/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' # 定义 Docker Compose 文件的版本,此处使用的是版本 3.7 2 | 3 | volumes: # 定义卷部分 4 | db_data: # docker 会使用这个键作为名字,自动创建 db_data 卷来存储数据 5 | db_data_test: 6 | 7 | services: # 定义服务部分 8 | 9 | db: # db 服务配置 10 | image: 'postgres:15-alpine' # 使用 PostgreSQL 15 Alpine 版本的镜像 11 | volumes: # 定义挂载卷 12 | - 'db_data:/var/lib/postgresql/data/pgdata' # 将 db_data 卷挂载到容器的 /var/lib/postgresql/data/pgdata 目录 13 | environment: # 定义环境变量 14 | PGDATA: '/var/lib/postgresql/data/pgdata' # 设置 PGDATA 环境变量为 /var/lib/postgresql/data/pgdata 15 | POSTGRES_USER: 'vapor_username' # 设置 POSTGRES_USER 环境变量为 vapor_username 16 | POSTGRES_PASSWORD: 'vapor_password' # 设置 POSTGRES_PASSWORD 环境变量为 vapor_password 17 | POSTGRES_DB: 'vapor_database' # 设置 POSTGRES_DB 环境变量为 vapor_database 18 | ports: # 定义端口映射,将主机的 5432 端口映射到容器的 5432 端口 19 | - '5432:5432' 20 | db_test: 21 | image: 'postgres:15-alpine' 22 | volumes: 23 | - 'db_data_test:/var/lib/postgresql/data/pgdata' 24 | environment: # 定义环境变量 25 | PGDATA: '/var/lib/postgresql/data/pgdata' 26 | POSTGRES_USER: 'vapor_username' 27 | POSTGRES_PASSWORD: 'vapor_password' 28 | POSTGRES_DB: 'vapor_database' 29 | ports: 30 | - '5442:5432' 31 | -------------------------------------------------------------------------------- /4/.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5432 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /4/.env.testing: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5442 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /4/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MicroBlog", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), 12 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | ] 22 | ), 23 | .testTarget(name: "AppTests", dependencies: [ 24 | .target(name: "App"), 25 | .product(name: "XCTVapor", package: "vapor"), 26 | ]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /4/Sources/App/Controllers/PostController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct PostController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | routes.group("posts") { posts in 7 | posts.get(use: index) 8 | posts.post(use: create) 9 | } 10 | } 11 | 12 | func create(req: Request) async throws -> Post { 13 | let postData = try req.content.decode(Post.CreateDTO.self) 14 | 15 | let post = Post(content: postData.content) 16 | 17 | try await post.create(on: req.db) 18 | 19 | return post 20 | } 21 | 22 | func index(req: Request) async throws -> [Post] { 23 | let posts = try await Post.query(on: req.db).all() 24 | 25 | return posts 26 | } 27 | } -------------------------------------------------------------------------------- /4/Sources/App/Migrations/1_CreatePost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | // 定义 CreatePost 结构体,实现 AsyncMigration 协议 4 | struct CreatePost: AsyncMigration { 5 | // 准备方法,在数据库上进行准备操作 6 | func prepare(on database: Database) async throws { 7 | // 创建 Post 表的数据库模式对象 8 | try await database.schema(Post.schema) 9 | .id() // 添加 id 列 10 | .field("content", .string, .required) // 添加 content 列,类型为字符串,不能为空 11 | .field("created_at", .datetime) // 添加 created_at 列,类型为日期时间 12 | .create() // 创建 Post 表 13 | } 14 | 15 | // 回滚方法,在数据库上进行回滚操作 16 | func revert(on database: Database) async throws { 17 | try await database.schema(Post.schema).delete() // 删除 Post 表的数据库模式对象 18 | } 19 | } -------------------------------------------------------------------------------- /4/Sources/App/Models/Post.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Post: Model, Content { 5 | // 数据库中的表名 6 | static let schema = "posts" 7 | 8 | // 唯一性标识符 9 | @ID(key: .id) 10 | var id: UUID? 11 | 12 | // 内容 13 | @Field(key: "content") 14 | var content: String 15 | 16 | // 创建时间 17 | @Timestamp(key: "created_at", on: .create) 18 | var createdAt: Date? 19 | 20 | init() { } 21 | 22 | init(id: UUID? = nil, content: String) { 23 | self.id = id 24 | self.content = content 25 | } 26 | } 27 | 28 | extension Post { 29 | struct CreateDTO: Content { 30 | let content: String 31 | } 32 | } -------------------------------------------------------------------------------- /4/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | public func configure(_ app: Application) throws { 6 | app.databases.use(.postgres(configuration: SQLPostgresConfiguration( 7 | hostname: Environment.get("DATABASE_HOST") ?? "localhost", 8 | port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, 9 | username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", 10 | password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", 11 | database: Environment.get("DATABASE_NAME") ?? "vapor_database", 12 | tls: .prefer(try .init(configuration: .clientDefault))) 13 | ), as: .psql) 14 | 15 | app.migrations.add([CreatePost()]) 16 | 17 | try app.register(collection: PostController()) 18 | } 19 | -------------------------------------------------------------------------------- /4/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | import FluentPostgresDriver 4 | 5 | let app = Application(.development) 6 | 7 | app.http.server.configuration.port = 8080 8 | 9 | defer { app.shutdown() } 10 | 11 | app.get { req async in 12 | "It works!" 13 | } 14 | 15 | try configure(app) 16 | 17 | try app.run() 18 | -------------------------------------------------------------------------------- /4/Tests/AppTests/PostTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class PostTests: XCTestCase { 5 | func testCreatePost() async throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | 14 | let postDTO = Post.CreateDTO(content: "Post created from test") 15 | 16 | try app.test(.POST, "posts", beforeRequest: { req in 17 | try req.content.encode(postDTO) 18 | }, afterResponse: { res in 19 | XCTAssertEqual(res.status, .ok) 20 | 21 | let post = try res.content.decode(Post.self) 22 | 23 | XCTAssertEqual(postDTO.content, post.content) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /4/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | volumes: 4 | db_data: 5 | db_data_test: 6 | 7 | services: 8 | 9 | db: 10 | image: 'postgres:15-alpine' 11 | volumes: 12 | - 'db_data:/var/lib/postgresql/data/pgdata' 13 | environment: 14 | PGDATA: '/var/lib/postgresql/data/pgdata' 15 | POSTGRES_USER: 'vapor_username' 16 | POSTGRES_PASSWORD: 'vapor_password' 17 | POSTGRES_DB: 'vapor_database' 18 | ports: 19 | - '5432:5432' 20 | db_test: 21 | image: 'postgres:15-alpine' 22 | volumes: 23 | - 'db_data_test:/var/lib/postgresql/data/pgdata' 24 | environment: 25 | PGDATA: '/var/lib/postgresql/data/pgdata' 26 | POSTGRES_USER: 'vapor_username' 27 | POSTGRES_PASSWORD: 'vapor_password' 28 | POSTGRES_DB: 'vapor_database' 29 | ports: 30 | - '5442:5432' 31 | -------------------------------------------------------------------------------- /5/.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5432 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /5/.env.testing: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5442 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /5/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MicroBlog", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), 12 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | ] 22 | ), 23 | .testTarget(name: "AppTests", dependencies: [ 24 | .target(name: "App"), 25 | .product(name: "XCTVapor", package: "vapor"), 26 | ]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /5/Sources/App/Controllers/PostController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct PostController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | routes.group("posts") { posts in 7 | posts.get(use: index) 8 | posts.post(use: create) 9 | } 10 | } 11 | 12 | func create(req: Request) async throws -> Post { 13 | let postData = try req.content.decode(Post.CreateDTO.self) 14 | 15 | let post = Post(content: postData.content) 16 | 17 | try await post.create(on: req.db) 18 | 19 | return post 20 | } 21 | 22 | func index(req: Request) async throws -> [Post] { 23 | let posts = try await Post.query(on: req.db).all() 24 | 25 | return posts 26 | } 27 | } -------------------------------------------------------------------------------- /5/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct UserController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | routes.group("users") { posts in 7 | posts.get(use: index) 8 | posts.post(use: create) 9 | } 10 | } 11 | 12 | func create(req: Request) async throws -> User { 13 | let postData = try req.content.decode(User.CreateDTO.self) 14 | 15 | let user = User(username: postData.username, passwordHash: try await req.password.async.hash(postData.password)) 16 | 17 | try await user.create(on: req.db) 18 | 19 | return user 20 | } 21 | 22 | func index(req: Request) async throws -> [User] { 23 | let users = try await User.query(on: req.db).all() 24 | 25 | return users 26 | } 27 | } -------------------------------------------------------------------------------- /5/Sources/App/Migrations/1_CreatePost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | // 定义 CreatePost 结构体,实现 AsyncMigration 协议 4 | struct CreatePost: AsyncMigration { 5 | // 准备方法,在数据库上进行准备操作 6 | func prepare(on database: Database) async throws { 7 | // 创建 Post 表的数据库模式对象 8 | try await database.schema(Post.schema) 9 | .id() // 添加 id 列 10 | .field("content", .string, .required) // 添加 content 列,类型为字符串,不能为空 11 | .field("created_at", .datetime) // 添加 created_at 列,类型为日期时间 12 | .create() // 创建 Post 表 13 | } 14 | 15 | // 回滚方法,在数据库上进行回滚操作 16 | func revert(on database: Database) async throws { 17 | try await database.schema(Post.schema).delete() // 删除 Post 表的数据库模式对象 18 | } 19 | } -------------------------------------------------------------------------------- /5/Sources/App/Migrations/2_CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema(User.schema) 6 | .id() 7 | .field("username", .string, .required) 8 | .field("password_hash", .string, .required) 9 | .field("created_at", .datetime) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema(User.schema).delete() 15 | } 16 | } -------------------------------------------------------------------------------- /5/Sources/App/Models/Post.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Post: Model, Content { 5 | // 数据库中的表名 6 | static let schema = "posts" 7 | 8 | // 唯一性标识符 9 | @ID(key: .id) 10 | var id: UUID? 11 | 12 | // 内容 13 | @Field(key: "content") 14 | var content: String 15 | 16 | // 创建时间 17 | @Timestamp(key: "created_at", on: .create) 18 | var createdAt: Date? 19 | 20 | init() { } 21 | 22 | init(id: UUID? = nil, content: String) { 23 | self.id = id 24 | self.content = content 25 | } 26 | } 27 | 28 | extension Post { 29 | struct CreateDTO: Content { 30 | let content: String 31 | } 32 | } -------------------------------------------------------------------------------- /5/Sources/App/Models/User.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class User: Model, Content { 5 | static let schema = "users" 6 | 7 | @ID(key: .id) 8 | var id: UUID? 9 | 10 | @Field(key: "username") 11 | var username: String 12 | 13 | @Field(key: "password_hash") 14 | var passwordHash: String 15 | 16 | @Timestamp(key: "created_at", on: .create) 17 | var createdAt: Date? 18 | 19 | init() {} 20 | 21 | init(id: UUID? = nil, username: String, passwordHash: String) { 22 | self.id = id 23 | self.username = username 24 | self.passwordHash = passwordHash 25 | } 26 | } 27 | 28 | extension User { 29 | struct CreateDTO: Content { 30 | let username: String 31 | let password: String 32 | } 33 | } -------------------------------------------------------------------------------- /5/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | public func configure(_ app: Application) throws { 6 | app.databases.use(.postgres(configuration: SQLPostgresConfiguration( 7 | hostname: Environment.get("DATABASE_HOST") ?? "localhost", 8 | port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, 9 | username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", 10 | password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", 11 | database: Environment.get("DATABASE_NAME") ?? "vapor_database", 12 | tls: .prefer(try .init(configuration: .clientDefault))) 13 | ), as: .psql) 14 | 15 | app.passwords.use(.bcrypt) 16 | 17 | app.migrations.add([CreatePost(), CreateUser()]) 18 | 19 | try app.register(collection: PostController()) 20 | try app.register(collection: UserController()) 21 | } 22 | -------------------------------------------------------------------------------- /5/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | import FluentPostgresDriver 4 | 5 | let app = Application(.development) 6 | 7 | app.http.server.configuration.port = 8080 8 | 9 | defer { app.shutdown() } 10 | 11 | app.get { req async in 12 | "It works!" 13 | } 14 | 15 | try configure(app) 16 | 17 | try app.run() 18 | -------------------------------------------------------------------------------- /5/Tests/AppTests/PostTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class PostTests: XCTestCase { 5 | func testCreatePost() async throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | 14 | let postDTO = Post.CreateDTO(content: "Post created from test") 15 | 16 | try app.test(.POST, "posts", beforeRequest: { req in 17 | try req.content.encode(postDTO) 18 | }, afterResponse: { res in 19 | XCTAssertEqual(res.status, .ok) 20 | 21 | let post = try res.content.decode(Post.self) 22 | 23 | XCTAssertEqual(postDTO.content, post.content) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5/Tests/AppTests/UserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class UserTests: XCTestCase { 5 | var app: Application! 6 | 7 | override func setUp() async throws { 8 | app = Application(.testing) 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | } 14 | 15 | 16 | override func tearDown() async throws { 17 | app.shutdown() 18 | } 19 | 20 | func testCreateUser() async throws { 21 | let userData = User.CreateDTO(username: "happyuser", password: "123456") 22 | try app.test(.POST, "users", beforeRequest: { req in 23 | try req.content.encode(userData) 24 | }, afterResponse: { res in 25 | XCTAssertEqual(res.status, .ok) 26 | 27 | let user = try res.content.decode(User.self) 28 | 29 | XCTAssertEqual(user.username, userData.username) 30 | XCTAssertTrue(try app.password.verify("123456", created: user.passwordHash)) 31 | }) 32 | } 33 | 34 | func testGetUsers() async throws { 35 | let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456")) 36 | try await user.save(on: app.db) 37 | try app.test(.GET, "users", afterResponse: { res in 38 | XCTAssertEqual(res.status, .ok) 39 | 40 | let users = try res.content.decode([User].self) 41 | 42 | XCTAssertEqual(users.count, 1) 43 | XCTAssertEqual(users[0].username, user.username) 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /5/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | volumes: 4 | db_data: 5 | db_data_test: 6 | 7 | services: 8 | 9 | db: 10 | image: 'postgres:15-alpine' 11 | volumes: 12 | - 'db_data:/var/lib/postgresql/data/pgdata' 13 | environment: 14 | PGDATA: '/var/lib/postgresql/data/pgdata' 15 | POSTGRES_USER: 'vapor_username' 16 | POSTGRES_PASSWORD: 'vapor_password' 17 | POSTGRES_DB: 'vapor_database' 18 | ports: 19 | - '5432:5432' 20 | db_test: 21 | image: 'postgres:15-alpine' 22 | volumes: 23 | - 'db_data_test:/var/lib/postgresql/data/pgdata' 24 | environment: 25 | PGDATA: '/var/lib/postgresql/data/pgdata' 26 | POSTGRES_USER: 'vapor_username' 27 | POSTGRES_PASSWORD: 'vapor_password' 28 | POSTGRES_DB: 'vapor_database' 29 | ports: 30 | - '5442:5432' 31 | -------------------------------------------------------------------------------- /6/.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5432 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /6/.env.testing: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=localhost 2 | DATABASE_PORT=5442 3 | DATABASE_USERNAME=vapor_username 4 | DATABASE_PASSWORD=vapor_password 5 | DATABASE_NAME=vapor_database -------------------------------------------------------------------------------- /6/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MicroBlog", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/vapor/vapor.git", from: "4.77.0"), 11 | .package(url: "https://github.com/vapor/fluent.git", from: "4.4.0"), 12 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "App", 17 | dependencies: [ 18 | .product(name: "Vapor", package: "vapor"), 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | ] 22 | ), 23 | .testTarget(name: "AppTests", dependencies: [ 24 | .target(name: "App"), 25 | .product(name: "XCTVapor", package: "vapor"), 26 | ]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /6/Sources/App/Controllers/PostController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct PostController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | routes.group("posts") { posts in 7 | posts.get(use: index) 8 | posts.grouped(User.authenticator()).post(use: create) 9 | } 10 | } 11 | 12 | func create(req: Request) async throws -> Post { 13 | let user = try req.auth.require(User.self) 14 | 15 | let postData = try req.content.decode(Post.CreateDTO.self) 16 | 17 | let post = Post(content: postData.content) 18 | 19 | post.$user.id = try user.requireID() 20 | 21 | try await post.create(on: req.db) 22 | 23 | return post 24 | } 25 | 26 | func index(req: Request) async throws -> [Post] { 27 | let posts = try await Post.query(on: req.db).all() 28 | 29 | return posts 30 | } 31 | } -------------------------------------------------------------------------------- /6/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct UserController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | routes.group("users") { posts in 7 | posts.get(use: index) 8 | posts.post(use: create) 9 | } 10 | } 11 | 12 | func create(req: Request) async throws -> User { 13 | let postData = try req.content.decode(User.CreateDTO.self) 14 | 15 | let user = User(username: postData.username, passwordHash: try await req.password.async.hash(postData.password)) 16 | 17 | try await user.create(on: req.db) 18 | 19 | return user 20 | } 21 | 22 | func index(req: Request) async throws -> [User] { 23 | let users = try await User.query(on: req.db).all() 24 | 25 | return users 26 | } 27 | } -------------------------------------------------------------------------------- /6/Sources/App/Migrations/1_CreatePost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreatePost: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema(Post.schema) 6 | .id() 7 | .field("content", .string, .required) 8 | .field("created_at", .datetime) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema(Post.schema).delete() 14 | } 15 | } -------------------------------------------------------------------------------- /6/Sources/App/Migrations/2_CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema(User.schema) 6 | .id() 7 | .field("username", .string, .required) 8 | .field("password_hash", .string, .required) 9 | .field("created_at", .datetime) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema(User.schema).delete() 15 | } 16 | } -------------------------------------------------------------------------------- /6/Sources/App/Migrations/3_AddUserIDToPost.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct AddUserIDToPost: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema(Post.schema) 6 | .field("user_id", .uuid, .references(User.schema, "id")) 7 | .update() 8 | } 9 | 10 | func revert(on database: Database) async throws { 11 | try await database.schema(Post.schema) 12 | .deleteField("user_id") 13 | .update() 14 | } 15 | } -------------------------------------------------------------------------------- /6/Sources/App/Models/Post.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class Post: Model, Content { 5 | static let schema = "posts" 6 | 7 | @ID(key: .id) 8 | var id: UUID? 9 | 10 | @OptionalParent(key: "user_id") 11 | var user: User? 12 | 13 | @Field(key: "content") 14 | var content: String 15 | 16 | @Timestamp(key: "created_at", on: .create) 17 | var createdAt: Date? 18 | 19 | init() { } 20 | 21 | init(id: UUID? = nil, content: String) { 22 | self.id = id 23 | self.content = content 24 | } 25 | } 26 | 27 | extension Post { 28 | struct CreateDTO: Content { 29 | let content: String 30 | } 31 | } -------------------------------------------------------------------------------- /6/Sources/App/Models/User.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class User: Model, Content { 5 | static let schema = "users" 6 | 7 | @ID(key: .id) 8 | var id: UUID? 9 | 10 | @Field(key: "username") 11 | var username: String 12 | 13 | @Field(key: "password_hash") 14 | var passwordHash: String 15 | 16 | @Children(for: \.$user) 17 | var posts: [Post] 18 | 19 | @Timestamp(key: "created_at", on: .create) 20 | var createdAt: Date? 21 | 22 | init() {} 23 | 24 | init(id: UUID? = nil, username: String, passwordHash: String) { 25 | self.id = id 26 | self.username = username 27 | self.passwordHash = passwordHash 28 | } 29 | } 30 | 31 | extension User { 32 | struct CreateDTO: Content { 33 | let username: String 34 | let password: String 35 | } 36 | } 37 | 38 | extension User: ModelAuthenticatable { 39 | static let usernameKey = \User.$username 40 | static let passwordHashKey = \User.$passwordHash 41 | 42 | func verify(password: String) throws -> Bool { 43 | try Bcrypt.verify(password, created: self.passwordHash) 44 | } 45 | } -------------------------------------------------------------------------------- /6/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | public func configure(_ app: Application) throws { 6 | app.databases.use(.postgres(configuration: SQLPostgresConfiguration( 7 | hostname: Environment.get("DATABASE_HOST") ?? "localhost", 8 | port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, 9 | username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", 10 | password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", 11 | database: Environment.get("DATABASE_NAME") ?? "vapor_database", 12 | tls: .prefer(try .init(configuration: .clientDefault))) 13 | ), as: .psql) 14 | 15 | app.passwords.use(.bcrypt) 16 | 17 | app.migrations.add([CreatePost(), CreateUser(), AddUserIDToPost()]) 18 | 19 | try app.register(collection: PostController()) 20 | try app.register(collection: UserController()) 21 | } 22 | -------------------------------------------------------------------------------- /6/Sources/App/main.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | import FluentPostgresDriver 4 | 5 | let app = Application(.development) 6 | 7 | app.http.server.configuration.port = 8080 8 | 9 | defer { app.shutdown() } 10 | 11 | app.get { req async in 12 | "It works!" 13 | } 14 | 15 | try configure(app) 16 | 17 | try app.run() 18 | -------------------------------------------------------------------------------- /6/Tests/AppTests/PostTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class PostTests: XCTestCase { 5 | var app: Application! 6 | 7 | override func setUp() async throws { 8 | app = Application(.testing) 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | } 14 | 15 | 16 | override func tearDown() async throws { 17 | app.shutdown() 18 | } 19 | 20 | 21 | func testCreatePost() async throws { 22 | let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456")) 23 | try await user.save(on: app.db) 24 | let postDTO = Post.CreateDTO(content: "Post created from test") 25 | 26 | try app.test(.POST, "posts", beforeRequest: { req in 27 | try req.content.encode(postDTO) 28 | req.headers.basicAuthorization = BasicAuthorization(username: user.username, password: "123456") 29 | }, afterResponse: { res in 30 | XCTAssertEqual(res.status, .ok) 31 | 32 | let post = try res.content.decode(Post.self) 33 | 34 | XCTAssertEqual(postDTO.content, post.content) 35 | XCTAssertEqual(try user.requireID(), post.$user.id) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /6/Tests/AppTests/UserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class UserTests: XCTestCase { 5 | var app: Application! 6 | 7 | override func setUp() async throws { 8 | app = Application(.testing) 9 | try configure(app) 10 | 11 | try await app.autoRevert() 12 | try await app.autoMigrate() 13 | } 14 | 15 | 16 | override func tearDown() async throws { 17 | app.shutdown() 18 | } 19 | 20 | func testCreateUser() async throws { 21 | let userData = User.CreateDTO(username: "happyuser", password: "123456") 22 | try app.test(.POST, "users", beforeRequest: { req in 23 | try req.content.encode(userData) 24 | }, afterResponse: { res in 25 | XCTAssertEqual(res.status, .ok) 26 | 27 | let user = try res.content.decode(User.self) 28 | 29 | XCTAssertEqual(user.username, userData.username) 30 | XCTAssertTrue(try app.password.verify("123456", created: user.passwordHash)) 31 | }) 32 | } 33 | 34 | func testGetUsers() async throws { 35 | let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456")) 36 | try await user.save(on: app.db) 37 | try app.test(.GET, "users", afterResponse: { res in 38 | XCTAssertEqual(res.status, .ok) 39 | 40 | let users = try res.content.decode([User].self) 41 | 42 | XCTAssertEqual(users.count, 1) 43 | XCTAssertEqual(users[0].username, user.username) 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /6/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | volumes: 4 | db_data: 5 | db_data_test: 6 | 7 | services: 8 | 9 | db: 10 | image: 'postgres:15-alpine' 11 | volumes: 12 | - 'db_data:/var/lib/postgresql/data/pgdata' 13 | environment: 14 | PGDATA: '/var/lib/postgresql/data/pgdata' 15 | POSTGRES_USER: 'vapor_username' 16 | POSTGRES_PASSWORD: 'vapor_password' 17 | POSTGRES_DB: 'vapor_database' 18 | ports: 19 | - '5432:5432' 20 | db_test: 21 | image: 'postgres:15-alpine' 22 | volumes: 23 | - 'db_data_test:/var/lib/postgresql/data/pgdata' 24 | environment: 25 | PGDATA: '/var/lib/postgresql/data/pgdata' 26 | POSTGRES_USER: 'vapor_username' 27 | POSTGRES_PASSWORD: 'vapor_password' 28 | POSTGRES_DB: 'vapor_database' 29 | ports: 30 | - '5442:5432' 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift on Server Tour 2 | 3 | Example codes about Swift On Server Tour series 4 | --------------------------------------------------------------------------------