└── Day1 ├── Morning ├── Public │ └── .gitkeep ├── Sources │ ├── App │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ ├── CategoriesControllers.swift │ │ │ ├── UserController.swift │ │ │ └── ReminderController.swift │ │ ├── Migrations │ │ │ ├── CreateCategory.swift │ │ │ ├── CreateUser.swift │ │ │ ├── CreateReminder.swift │ │ │ └── CreateReminderCategoryPivot.swift │ │ ├── Models │ │ │ ├── ReminderCategoryPivot.swift │ │ │ ├── Category.swift │ │ │ ├── User.swift │ │ │ └── Reminder.swift │ │ ├── configure.swift │ │ └── routes.swift │ └── Run │ │ └── main.swift ├── .dockerignore ├── .gitignore ├── Tests │ └── AppTests │ │ └── AppTests.swift ├── Package.swift ├── docker-compose.yml ├── Dockerfile └── Package.resolved └── Afternoon ├── final ├── Public │ └── .gitkeep ├── Sources │ ├── App │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ ├── LeafController.swift │ │ │ ├── CategoriesControllers.swift │ │ │ ├── ReminderController.swift │ │ │ └── UserController.swift │ │ ├── Migrations │ │ │ ├── CreateCategory.swift │ │ │ ├── CreateDefaultUser.swift │ │ │ ├── CreateUser.swift │ │ │ ├── CreateReminder.swift │ │ │ ├── CreateToken.swift │ │ │ └── CreateReminderCategoryPivot.swift │ │ ├── Models │ │ │ ├── Category.swift │ │ │ ├── ReminderCategoryPivot.swift │ │ │ ├── Reminder.swift │ │ │ ├── Token.swift │ │ │ └── User.swift │ │ ├── configure.swift │ │ └── routes.swift │ └── Run │ │ └── main.swift ├── .gitignore ├── Resources │ └── Views │ │ ├── base.leaf │ │ └── index.leaf ├── Tests │ └── AppTests │ │ └── AppTests.swift ├── Package.swift ├── docker-compose.yml ├── Dockerfile └── Package.resolved └── starter ├── Public └── .gitkeep ├── Sources ├── App │ ├── Controllers │ │ ├── .gitkeep │ │ ├── CategoriesControllers.swift │ │ ├── UserController.swift │ │ └── ReminderController.swift │ ├── Migrations │ │ ├── CreateCategory.swift │ │ ├── CreateUser.swift │ │ ├── CreateReminder.swift │ │ └── CreateReminderCategoryPivot.swift │ ├── Models │ │ ├── Category.swift │ │ ├── ReminderCategoryPivot.swift │ │ ├── User.swift │ │ └── Reminder.swift │ ├── configure.swift │ └── routes.swift └── Run │ └── main.swift ├── .dockerignore ├── .gitignore ├── Tests └── AppTests │ └── AppTests.swift ├── Package.swift ├── docker-compose.yml ├── Dockerfile └── Package.resolved /Day1/Morning/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Morning/.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .build 3 | DerivedData 4 | Package.resolved 5 | *.xcodeproj 6 | 7 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | .swiftpm 8 | -------------------------------------------------------------------------------- /Day1/Morning/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | .env 10 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | DerivedData/ 6 | .DS_Store 7 | db.sqlite 8 | .swiftpm 9 | .env 10 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import Vapor 3 | 4 | var env = try Environment.detect() 5 | try LoggingSystem.bootstrap(from: &env) 6 | let app = Application(env) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | try app.run() 10 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Resources/Views/base.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #(title) | Reminders 7 | 8 | 9 | 10 | #import("content") 11 | 12 | 13 | -------------------------------------------------------------------------------- /Day1/Morning/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello", afterResponse: { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello", afterResponse: { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | @testable import App 2 | import XCTVapor 3 | 4 | final class AppTests: XCTestCase { 5 | func testHelloWorld() throws { 6 | let app = Application(.testing) 7 | defer { app.shutdown() } 8 | try configure(app) 9 | 10 | try app.test(.GET, "hello", afterResponse: { res in 11 | XCTAssertEqual(res.status, .ok) 12 | XCTAssertEqual(res.body.string, "Hello, world!") 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Migrations/CreateCategory.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateCategory: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("categories") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("name", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) async throws { 12 | try await database.schema("categories").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Migrations/CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("users") 6 | .id() 7 | .field("name", .string, .required) 8 | .field("username", .string, .required) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema("users").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateCategory.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateCategory: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("categories") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("name", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) async throws { 12 | try await database.schema("categories").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Migrations/CreateCategory.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateCategory: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("categories") 6 | .field("id", .int, .identifier(auto: true)) 7 | .field("name", .string, .required) 8 | .create() 9 | } 10 | 11 | func revert(on database: Database) async throws { 12 | try await database.schema("categories").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Migrations/CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("users") 6 | .id() 7 | .field("name", .string, .required) 8 | .field("username", .string, .required) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema("users").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateDefaultUser.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct CreateDefaultUser: AsyncMigration { 5 | func prepare(on database: Database) async throws { 6 | let password = try Bcrypt.hash("password") 7 | let user = User(name: "default", username: "default", passwordHash: password) 8 | try await user.create(on: database) 9 | } 10 | 11 | func revert(on database: any Database) async throws { 12 | try await User.query(on: database).filter(\.$username == "default").delete() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateUser.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateUser: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("users") 6 | .id() 7 | .field("name", .string, .required) 8 | .field("username", .string, .required) 9 | .field("passwordHash", .string, .required) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema("users").delete() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Migrations/CreateReminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminder: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminders") 6 | .id() 7 | .field("title", .string, .required) 8 | .field("userID", .uuid, .required, 9 | .references("users", "id")) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema("reminders").delete() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Models/ReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | final class ReminderCategoryPivot: Model { 4 | static let schema = "reminder+category" 5 | 6 | @ID 7 | var id: UUID? 8 | 9 | @Parent(key: "reminderID") 10 | var reminder: Reminder 11 | 12 | @Parent(key: "categoryID") 13 | var category: Category 14 | 15 | init() {} 16 | 17 | init(reminderID: Reminder.IDValue, categoryID: Category.IDValue) { 18 | self.$reminder.id = reminderID 19 | self.$category.id = categoryID 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateReminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminder: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminders") 6 | .id() 7 | .field("title", .string, .required) 8 | .field("userID", .uuid, .required, 9 | .references("users", "id")) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema("reminders").delete() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Migrations/CreateReminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminder: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminders") 6 | .id() 7 | .field("title", .string, .required) 8 | .field("userID", .uuid, .required, 9 | .references("users", "id")) 10 | .create() 11 | } 12 | 13 | func revert(on database: Database) async throws { 14 | try await database.schema("reminders").delete() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Models/Category.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Category: Model, Content { 5 | static let schema = "categories" 6 | 7 | @ID(custom: "id") 8 | var id: Int? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Siblings(through: ReminderCategoryPivot.self, 14 | from: \.$category, to: \.$reminder) 15 | var reminders: [Reminder] 16 | 17 | init() {} 18 | 19 | init(id: Int? = nil, name: String) { 20 | self.id = id 21 | self.name = name 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Resources/Views/index.leaf: -------------------------------------------------------------------------------- 1 | #extend("base"): 2 | #export("content"): 3 |

Reminders

4 | #if(count(reminders) > 0): 5 | 6 | 7 | 8 | #for(reminder in reminders): 9 | 10 | #endfor 11 | 12 |
NumberTitle
#(reminder.id)#(reminder.title)
13 | #else: 14 |

You haven't created any reminder yet!

15 | #endif 16 | 17 | 18 | #endexport 19 | #endextend 20 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Models/User.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class User: Model, Content { 5 | static let schema = "users" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Field(key: "username") 14 | var username: String 15 | 16 | @Children(for: \.$user) 17 | var reminders: [Reminder] 18 | 19 | init() {} 20 | 21 | init(id: UUID? = nil, name: String, username: String) { 22 | self.id = id 23 | self.name = name 24 | self.username = username 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Models/Category.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Category: Model, Content, @unchecked Sendable { 5 | static let schema = "categories" 6 | 7 | @ID(custom: "id") 8 | var id: Int? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Siblings(through: ReminderCategoryPivot.self, 14 | from: \.$category, to: \.$reminder) 15 | var reminders: [Reminder] 16 | 17 | init() {} 18 | 19 | init(id: Int? = nil, name: String) { 20 | self.id = id 21 | self.name = name 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Models/Category.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Category: Model, Content, @unchecked Sendable { 5 | static let schema = "categories" 6 | 7 | @ID(custom: "id") 8 | var id: Int? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Siblings(through: ReminderCategoryPivot.self, 14 | from: \.$category, to: \.$reminder) 15 | var reminders: [Reminder] 16 | 17 | init() {} 18 | 19 | init(id: Int? = nil, name: String) { 20 | self.id = id 21 | self.name = name 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Models/ReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Foundation 3 | 4 | final class ReminderCategoryPivot: Model, @unchecked Sendable { 5 | static let schema = "reminder+category" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Parent(key: "reminderID") 11 | var reminder: Reminder 12 | 13 | @Parent(key: "categoryID") 14 | var category: Category 15 | 16 | init() {} 17 | 18 | init(reminderID: Reminder.IDValue, categoryID: Category.IDValue) { 19 | self.$reminder.id = reminderID 20 | self.$category.id = categoryID 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Controllers/LeafController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct LeafController: RouteCollection { 4 | func boot(routes: any RoutesBuilder) throws { 5 | routes.get(use: indexHandler) 6 | } 7 | 8 | func indexHandler(_ req: Request) async throws -> View { 9 | let reminders = try await Reminder.query(on: req.db).all() 10 | let context = IndexConxt(title: "Homepage", reminders: reminders) 11 | return try await req.view.render("index", context) 12 | } 13 | } 14 | 15 | struct IndexConxt: Encodable { 16 | let title: String 17 | let reminders: [Reminder] 18 | } 19 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Models/ReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Foundation 3 | 4 | final class ReminderCategoryPivot: Model, @unchecked Sendable { 5 | static let schema = "reminder+category" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Parent(key: "reminderID") 11 | var reminder: Reminder 12 | 13 | @Parent(key: "categoryID") 14 | var category: Category 15 | 16 | init() {} 17 | 18 | init(reminderID: Reminder.IDValue, categoryID: Category.IDValue) { 19 | self.$reminder.id = reminderID 20 | self.$category.id = categoryID 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Models/User.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class User: Model, Content, @unchecked Sendable { 5 | static let schema = "users" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Field(key: "username") 14 | var username: String 15 | 16 | @Children(for: \.$user) 17 | var reminders: [Reminder] 18 | 19 | init() {} 20 | 21 | init(id: UUID? = nil, name: String, username: String) { 22 | self.id = id 23 | self.name = name 24 | self.username = username 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateToken.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateToken: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("tokens") 6 | .id() 7 | .field("token_value", .string, .required) 8 | .field("user_id", .uuid, .required, .references("users", "id")) 9 | .field("expires_at", .date, .required) 10 | .unique(on: "token_value") 11 | .create() 12 | } 13 | 14 | func revert(on database: Database) async throws { 15 | try await database.schema("tokens").delete() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Migrations/CreateReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminderCategoryPivot: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminder+category") 6 | .id() 7 | .field("reminderID", .uuid, .required, .references("reminders", "id", onDelete: .cascade)) 8 | .field("categoryID", .int, .required, .references("categories", "id", onDelete: .cascade)) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema("reminder+category").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Migrations/CreateReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminderCategoryPivot: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminder+category") 6 | .id() 7 | .field("reminderID", .uuid, .required, .references("reminders", "id", onDelete: .cascade)) 8 | .field("categoryID", .int, .required, .references("categories", "id", onDelete: .cascade)) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema("reminder+category").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Migrations/CreateReminderCategoryPivot.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | 3 | struct CreateReminderCategoryPivot: AsyncMigration { 4 | func prepare(on database: Database) async throws { 5 | try await database.schema("reminder+category") 6 | .id() 7 | .field("reminderID", .uuid, .required, .references("reminders", "id", onDelete: .cascade)) 8 | .field("categoryID", .int, .required, .references("categories", "id", onDelete: .cascade)) 9 | .create() 10 | } 11 | 12 | func revert(on database: Database) async throws { 13 | try await database.schema("reminder+category").delete() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Models/Reminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Reminder: Model, Content { 5 | static let schema = "reminders" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | @Parent(key: "userID") 14 | var user: User 15 | 16 | @Siblings(through: ReminderCategoryPivot.self, 17 | from: \.$reminder, to: \.$category) 18 | var categories: [Category] 19 | 20 | init() {} 21 | init(id: UUID? = nil, title: String, userID: User.IDValue) { 22 | self.id = id 23 | self.title = title 24 | self.$user.id = userID 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Models/Reminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Reminder: Model, Content, @unchecked Sendable { 5 | static let schema = "reminders" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | @Parent(key: "userID") 14 | var user: User 15 | 16 | @Siblings(through: ReminderCategoryPivot.self, 17 | from: \.$reminder, to: \.$category) 18 | var categories: [Category] 19 | 20 | init() {} 21 | init(id: UUID? = nil, title: String, userID: User.IDValue) { 22 | self.id = id 23 | self.title = title 24 | self.$user.id = userID 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Models/Reminder.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Reminder: Model, Content, @unchecked Sendable { 5 | static let schema = "reminders" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "title") 11 | var title: String 12 | 13 | @Parent(key: "userID") 14 | var user: User 15 | 16 | @Siblings(through: ReminderCategoryPivot.self, 17 | from: \.$reminder, to: \.$category) 18 | var categories: [Category] 19 | 20 | init() {} 21 | init(id: UUID? = nil, title: String, userID: User.IDValue) { 22 | self.id = id 23 | self.title = title 24 | self.$user.id = userID 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | 5 | // configures your application 6 | public func configure(_ app: Application) throws { 7 | // uncomment to serve files from /Public folder 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite) 11 | 12 | app.migrations.add(CreateUser()) 13 | app.migrations.add(CreateReminder()) 14 | app.migrations.add(CreateCategory()) 15 | app.migrations.add(CreateReminderCategoryPivot()) 16 | 17 | try app.autoMigrate().wait() 18 | 19 | // register routes 20 | try routes(app) 21 | } 22 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentSQLiteDriver 3 | import Vapor 4 | import Leaf 5 | 6 | // configures your application 7 | public func configure(_ app: Application) throws { 8 | // uncomment to serve files from /Public folder 9 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 10 | 11 | app.databases.use(DatabaseConfigurationFactory.sqlite(.file("db.sqlite")), as: .sqlite) 12 | 13 | app.migrations.add(CreateUser()) 14 | app.migrations.add(CreateReminder()) 15 | app.migrations.add(CreateCategory()) 16 | app.migrations.add(CreateReminderCategoryPivot()) 17 | app.migrations.add(CreateToken()) 18 | app.migrations.add(CreateDefaultUser()) 19 | 20 | try app.autoMigrate().wait() 21 | 22 | app.views.use(.leaf) 23 | 24 | // register routes 25 | try routes(app) 26 | } 27 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Models/Token.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class Token: Model, Content, @unchecked Sendable { 5 | static let schema = "tokens" 6 | 7 | @ID(key: .id) 8 | var id: UUID? 9 | 10 | @Field(key: "token_value") 11 | var value: String 12 | 13 | @Parent(key: "user_id") 14 | var user: User 15 | 16 | @Field(key: "expires_at") 17 | var expiresAt: Date 18 | 19 | init() { } 20 | 21 | init(id: UUID? = nil, value: String, userID: User.IDValue) { 22 | self.id = id 23 | self.value = value 24 | self.$user.id = userID 25 | // Set expirty to 30 days 26 | self.expiresAt = Date().advanced(by: 60 * 60 * 24 * 30) 27 | } 28 | } 29 | 30 | extension Token: ModelTokenAuthenticatable { 31 | typealias User = App.User 32 | static let valueKey = \Token.$value 33 | static let userKey = \Token.$user 34 | 35 | var isValid: Bool { 36 | self.expiresAt > Date() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import FluentPostgresDriver 3 | import Vapor 4 | 5 | // configures your application 6 | public func configure(_ app: Application) throws { 7 | // uncomment to serve files from /Public folder 8 | // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) 9 | 10 | app.databases.use(DatabaseConfigurationFactory.postgres(configuration: .init( 11 | hostname: Environment.get("DATABASE_HOST") ?? "localhost", 12 | port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, 13 | username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", 14 | password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", 15 | database: Environment.get("DATABASE_NAME") ?? "vapor_database", 16 | tls: .prefer(try .init(configuration: .clientDefault))) 17 | ), as: .psql) 18 | 19 | app.migrations.add(CreateUser()) 20 | app.migrations.add(CreateReminder()) 21 | app.migrations.add(CreateCategory()) 22 | app.migrations.add(CreateReminderCategoryPivot()) 23 | 24 | try app.autoMigrate().wait() 25 | 26 | // register routes 27 | try routes(app) 28 | } 29 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Models/User.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | final class User: Model, Content, @unchecked Sendable { 5 | static let schema = "users" 6 | 7 | @ID 8 | var id: UUID? 9 | 10 | @Field(key: "name") 11 | var name: String 12 | 13 | @Field(key: "username") 14 | var username: String 15 | 16 | @Children(for: \.$user) 17 | var reminders: [Reminder] 18 | 19 | @Field(key: "passwordHash") 20 | var passwordHash: String 21 | 22 | init() {} 23 | 24 | init(id: UUID? = nil, name: String, username: String, passwordHash: String) { 25 | self.id = id 26 | self.name = name 27 | self.username = username 28 | self.passwordHash = passwordHash 29 | } 30 | } 31 | 32 | extension User: ModelAuthenticatable { 33 | static let usernameKey = \User.$username 34 | static let passwordHashKey = \User.$passwordHash 35 | 36 | func verify(password: String) throws -> Bool { 37 | try Bcrypt.verify(password, created: self.passwordHash) 38 | } 39 | } 40 | 41 | extension User { 42 | func generateToken() throws -> Token { 43 | try .init( 44 | value: [UInt8].random(count: 32).base64, 45 | userID: self.requireID() 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Controllers/CategoriesControllers.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct CategoriesController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | let categoriesRoutes = routes.grouped("api", "categories") 7 | categoriesRoutes.post(use: createHandler) 8 | categoriesRoutes.get(use: getAllHandler) 9 | categoriesRoutes.get(":categoryID", use: getHandler) 10 | categoriesRoutes.get(":categoryID", "reminders", use: getRemindersHandler) 11 | } 12 | 13 | func createHandler(req: Request) async throws -> Category { 14 | let category = try req.content.decode(Category.self) 15 | try await category.save(on: req.db) 16 | return category 17 | } 18 | 19 | func getAllHandler(req: Request) async throws -> [Category] { 20 | try await Category.query(on: req.db).all() 21 | } 22 | 23 | func getHandler(req: Request) async throws -> Category { 24 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 25 | throw Abort(.notFound) 26 | } 27 | return category 28 | } 29 | 30 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 31 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 32 | throw Abort(.notFound) 33 | } 34 | return try await category.$reminders.get(on: req.db) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Controllers/CategoriesControllers.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct CategoriesController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | let categoriesRoutes = routes.grouped("api", "categories") 7 | categoriesRoutes.post(use: createHandler) 8 | categoriesRoutes.get(use: getAllHandler) 9 | categoriesRoutes.get(":categoryID", use: getHandler) 10 | categoriesRoutes.get(":categoryID", "reminders", use: getRemindersHandler) 11 | } 12 | 13 | func createHandler(req: Request) async throws -> Category { 14 | let category = try req.content.decode(Category.self) 15 | try await category.save(on: req.db) 16 | return category 17 | } 18 | 19 | func getAllHandler(req: Request) async throws -> [Category] { 20 | try await Category.query(on: req.db).all() 21 | } 22 | 23 | func getHandler(req: Request) async throws -> Category { 24 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 25 | throw Abort(.notFound) 26 | } 27 | return category 28 | } 29 | 30 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 31 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 32 | throw Abort(.notFound) 33 | } 34 | return try await category.$reminders.get(on: req.db) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Controllers/CategoriesControllers.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | struct CategoriesController: RouteCollection { 5 | func boot(routes: RoutesBuilder) throws { 6 | let categoriesRoutes = routes.grouped("api", "categories") 7 | categoriesRoutes.post(use: createHandler) 8 | categoriesRoutes.get(use: getAllHandler) 9 | categoriesRoutes.get(":categoryID", use: getHandler) 10 | categoriesRoutes.get(":categoryID", "reminders", use: getRemindersHandler) 11 | } 12 | 13 | func createHandler(req: Request) async throws -> Category { 14 | let category = try req.content.decode(Category.self) 15 | try await category.save(on: req.db) 16 | return category 17 | } 18 | 19 | func getAllHandler(req: Request) async throws -> [Category] { 20 | try await Category.query(on: req.db).all() 21 | } 22 | 23 | func getHandler(req: Request) async throws -> Category { 24 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 25 | throw Abort(.notFound) 26 | } 27 | return category 28 | } 29 | 30 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 31 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 32 | throw Abort(.notFound) 33 | } 34 | return try await category.$reminders.get(on: req.db) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get("hello") { req -> String in 6 | return "Hello, world!" 7 | } 8 | 9 | app.get("hello", "workshop") { req in 10 | return "Hello Vapor Workshop!" 11 | } 12 | 13 | app.get("bottles", ":count") { req -> Bottles in 14 | let count = try req.parameters.require("count", as: Int.self) 15 | return Bottles(count: count) 16 | } 17 | 18 | app.get("hello", ":name") { req -> String in 19 | let name = try req.parameters.require("name") 20 | return "Hello \(name)" 21 | } 22 | 23 | app.post("bottles") { req -> String in 24 | let bottles = try req.content.decode(Bottles.self) 25 | return "There were \(bottles.count) bottles" 26 | } 27 | 28 | app.post("user-info") { req -> UserMessage in 29 | let userInfo = try req.content.decode(UserInfo.self) 30 | let message = "Hello \(userInfo.name), you are \(userInfo.age)" 31 | return UserMessage(message: message) 32 | } 33 | 34 | try app.register(collection: UserController()) 35 | try app.register(collection: RemindersController()) 36 | try app.register(collection: CategoriesController()) 37 | try app.register(collection: LeafController()) 38 | } 39 | 40 | struct Bottles: Content { 41 | let count: Int 42 | } 43 | 44 | struct UserInfo: Content { 45 | let name: String 46 | let age: Int 47 | } 48 | 49 | struct UserMessage: Content { 50 | let message: String 51 | } 52 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("hello") { req -> String in 10 | return "Hello, world!" 11 | } 12 | 13 | app.get("hello", "workshop") { req in 14 | return "Hello Vapor Workshop!" 15 | } 16 | 17 | app.get("bottles", ":count") { req -> Bottles in 18 | let count = try req.parameters.require("count", as: Int.self) 19 | return Bottles(count: count) 20 | } 21 | 22 | app.get("hello", ":name") { req -> String in 23 | let name = try req.parameters.require("name") 24 | return "Hello \(name)" 25 | } 26 | 27 | app.post("bottles") { req -> String in 28 | let bottles = try req.content.decode(Bottles.self) 29 | return "There were \(bottles.count) bottles" 30 | } 31 | 32 | app.post("user-info") { req -> UserMessage in 33 | let userInfo = try req.content.decode(UserInfo.self) 34 | let message = "Hello \(userInfo.name), you are \(userInfo.age)" 35 | return UserMessage(message: message) 36 | } 37 | 38 | try app.register(collection: UserController()) 39 | try app.register(collection: RemindersController()) 40 | try app.register(collection: CategoriesController()) 41 | } 42 | 43 | struct Bottles: Content { 44 | let count: Int 45 | } 46 | 47 | struct UserInfo: Content { 48 | let name: String 49 | let age: Int 50 | } 51 | 52 | struct UserMessage: Content { 53 | let message: String 54 | } 55 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | func routes(_ app: Application) throws { 5 | app.get { req in 6 | return "It works!" 7 | } 8 | 9 | app.get("hello") { req -> String in 10 | return "Hello, world!" 11 | } 12 | 13 | app.get("hello", "workshop") { req in 14 | return "Hello Vapor Workshop!" 15 | } 16 | 17 | app.get("bottles", ":count") { req -> Bottles in 18 | let count = try req.parameters.require("count", as: Int.self) 19 | return Bottles(count: count) 20 | } 21 | 22 | app.get("hello", ":name") { req -> String in 23 | let name = try req.parameters.require("name") 24 | return "Hello \(name)" 25 | } 26 | 27 | app.post("bottles") { req -> String in 28 | let bottles = try req.content.decode(Bottles.self) 29 | return "There were \(bottles.count) bottles" 30 | } 31 | 32 | app.post("user-info") { req -> UserMessage in 33 | let userInfo = try req.content.decode(UserInfo.self) 34 | let message = "Hello \(userInfo.name), you are \(userInfo.age)" 35 | return UserMessage(message: message) 36 | } 37 | 38 | try app.register(collection: UserController()) 39 | try app.register(collection: RemindersController()) 40 | try app.register(collection: CategoriesController()) 41 | } 42 | 43 | struct Bottles: Content { 44 | let count: Int 45 | } 46 | 47 | struct UserInfo: Content { 48 | let name: String 49 | let age: Int 50 | } 51 | 52 | struct UserMessage: Content { 53 | let message: String 54 | } 55 | -------------------------------------------------------------------------------- /Day1/Morning/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HelloVapor", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "App", 18 | dependencies: [ 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), 21 | .product(name: "Vapor", package: "vapor") 22 | ], 23 | swiftSettings: [ 24 | // Enable better optimizations when building in Release configuration. Despite the use of 25 | // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release 26 | // builds. See for details. 27 | .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) 28 | ] 29 | ), 30 | .executableTarget(name: "Run", dependencies: [.target(name: "App")]), 31 | .testTarget(name: "AppTests", dependencies: [ 32 | .target(name: "App"), 33 | .product(name: "XCTVapor", package: "vapor"), 34 | ]) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HelloVapor", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 14 | ], 15 | targets: [ 16 | .target( 17 | name: "App", 18 | dependencies: [ 19 | .product(name: "Fluent", package: "fluent"), 20 | .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"), 21 | .product(name: "Vapor", package: "vapor") 22 | ], 23 | swiftSettings: [ 24 | // Enable better optimizations when building in Release configuration. Despite the use of 25 | // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release 26 | // builds. See for details. 27 | .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) 28 | ] 29 | ), 30 | .executableTarget(name: "Run", dependencies: [.target(name: "App")]), 31 | .testTarget(name: "AppTests", dependencies: [ 32 | .target(name: "App"), 33 | .product(name: "XCTVapor", package: "vapor"), 34 | ]) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HelloVapor", 6 | platforms: [ 7 | .macOS(.v12) 8 | ], 9 | dependencies: [ 10 | // 💧 A server-side Swift web framework. 11 | .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), 12 | .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), 13 | .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), 14 | .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "App", 19 | dependencies: [ 20 | .product(name: "Fluent", package: "fluent"), 21 | .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"), 22 | .product(name: "Vapor", package: "vapor"), 23 | .product(name: "Leaf", package: "leaf"), 24 | ], 25 | swiftSettings: [ 26 | // Enable better optimizations when building in Release configuration. Despite the use of 27 | // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release 28 | // builds. See for details. 29 | .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) 30 | ] 31 | ), 32 | .executableTarget(name: "Run", dependencies: [.target(name: "App")]), 33 | .testTarget(name: "AppTests", dependencies: [ 34 | .target(name: "App"), 35 | .product(name: "XCTVapor", package: "vapor"), 36 | ]) 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /Day1/Morning/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose run migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | DATABASE_HOST: db 25 | DATABASE_NAME: vapor_database 26 | DATABASE_USERNAME: vapor_username 27 | DATABASE_PASSWORD: vapor_password 28 | 29 | services: 30 | app: 31 | image: hello-vapor:latest 32 | build: 33 | context: . 34 | environment: 35 | <<: *shared_environment 36 | depends_on: 37 | - db 38 | ports: 39 | - '8080:8080' 40 | # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user. 41 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 42 | migrate: 43 | image: hello-vapor:latest 44 | build: 45 | context: . 46 | environment: 47 | <<: *shared_environment 48 | depends_on: 49 | - db 50 | command: ["migrate", "--yes"] 51 | deploy: 52 | replicas: 0 53 | revert: 54 | image: hello-vapor:latest 55 | build: 56 | context: . 57 | environment: 58 | <<: *shared_environment 59 | depends_on: 60 | - db 61 | command: ["migrate", "--revert", "--yes"] 62 | deploy: 63 | replicas: 0 64 | db: 65 | image: postgres:14-alpine 66 | volumes: 67 | - db_data:/var/lib/postgresql/data/pgdata 68 | environment: 69 | PGDATA: /var/lib/postgresql/data/pgdata 70 | POSTGRES_USER: vapor_username 71 | POSTGRES_PASSWORD: vapor_password 72 | POSTGRES_DB: vapor_database 73 | ports: 74 | - '5432:5432' 75 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose run migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | DATABASE_HOST: db 25 | DATABASE_NAME: vapor_database 26 | DATABASE_USERNAME: vapor_username 27 | DATABASE_PASSWORD: vapor_password 28 | 29 | services: 30 | app: 31 | image: hello-vapor:latest 32 | build: 33 | context: . 34 | environment: 35 | <<: *shared_environment 36 | depends_on: 37 | - db 38 | ports: 39 | - '8080:8080' 40 | # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user. 41 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 42 | migrate: 43 | image: hello-vapor:latest 44 | build: 45 | context: . 46 | environment: 47 | <<: *shared_environment 48 | depends_on: 49 | - db 50 | command: ["migrate", "--yes"] 51 | deploy: 52 | replicas: 0 53 | revert: 54 | image: hello-vapor:latest 55 | build: 56 | context: . 57 | environment: 58 | <<: *shared_environment 59 | depends_on: 60 | - db 61 | command: ["migrate", "--revert", "--yes"] 62 | deploy: 63 | replicas: 0 64 | db: 65 | image: postgres:14-alpine 66 | volumes: 67 | - db_data:/var/lib/postgresql/data/pgdata 68 | environment: 69 | PGDATA: /var/lib/postgresql/data/pgdata 70 | POSTGRES_USER: vapor_username 71 | POSTGRES_PASSWORD: vapor_password 72 | POSTGRES_DB: vapor_database 73 | ports: 74 | - '5432:5432' 75 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose file for Vapor 2 | # 3 | # Install Docker on your system to run and test 4 | # your Vapor app in a production-like environment. 5 | # 6 | # Note: This file is intended for testing and does not 7 | # implement best practices for a production deployment. 8 | # 9 | # Learn more: https://docs.docker.com/compose/reference/ 10 | # 11 | # Build images: docker-compose build 12 | # Start app: docker-compose up app 13 | # Start database: docker-compose up db 14 | # Run migrations: docker-compose run migrate 15 | # Stop all: docker-compose down (add -v to wipe db) 16 | # 17 | version: '3.7' 18 | 19 | volumes: 20 | db_data: 21 | 22 | x-shared_environment: &shared_environment 23 | LOG_LEVEL: ${LOG_LEVEL:-debug} 24 | DATABASE_HOST: db 25 | DATABASE_NAME: vapor_database 26 | DATABASE_USERNAME: vapor_username 27 | DATABASE_PASSWORD: vapor_password 28 | 29 | services: 30 | app: 31 | image: hello-vapor:latest 32 | build: 33 | context: . 34 | environment: 35 | <<: *shared_environment 36 | depends_on: 37 | - db 38 | ports: 39 | - '8080:8080' 40 | # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user. 41 | command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 42 | migrate: 43 | image: hello-vapor:latest 44 | build: 45 | context: . 46 | environment: 47 | <<: *shared_environment 48 | depends_on: 49 | - db 50 | command: ["migrate", "--yes"] 51 | deploy: 52 | replicas: 0 53 | revert: 54 | image: hello-vapor:latest 55 | build: 56 | context: . 57 | environment: 58 | <<: *shared_environment 59 | depends_on: 60 | - db 61 | command: ["migrate", "--revert", "--yes"] 62 | deploy: 63 | replicas: 0 64 | db: 65 | image: postgres:14-alpine 66 | volumes: 67 | - db_data:/var/lib/postgresql/data/pgdata 68 | environment: 69 | PGDATA: /var/lib/postgresql/data/pgdata 70 | POSTGRES_USER: vapor_username 71 | POSTGRES_PASSWORD: vapor_password 72 | POSTGRES_DB: vapor_database 73 | ports: 74 | - '5432:5432' 75 | -------------------------------------------------------------------------------- /Day1/Morning/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM swift:5.5-focal as build 5 | 6 | # Install OS updates and, if needed, sqlite3 7 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 8 | && apt-get -q update \ 9 | && apt-get -q dist-upgrade -y \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | # Set up a build area 13 | WORKDIR /build 14 | 15 | # First just resolve dependencies. 16 | # This creates a cached layer that can be reused 17 | # as long as your Package.swift/Package.resolved 18 | # files do not change. 19 | COPY ./Package.* ./ 20 | RUN swift package resolve 21 | 22 | # Copy entire repo into container 23 | COPY . . 24 | 25 | # Build everything, with optimizations 26 | RUN swift build -c release --static-swift-stdlib 27 | 28 | # Switch to the staging area 29 | WORKDIR /staging 30 | 31 | # Copy main executable to staging area 32 | RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./ 33 | 34 | # Copy any resources from the public directory and views directory if the directories exist 35 | # Ensure that by default, neither the directory nor any of its contents are writable. 36 | RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true 37 | RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true 38 | 39 | # ================================ 40 | # Run image 41 | # ================================ 42 | FROM ubuntu:focal 43 | 44 | # Make sure all system packages are up to date. 45 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true && \ 46 | apt-get -q update && apt-get -q dist-upgrade -y && apt-get -q install -y ca-certificates && \ 47 | rm -r /var/lib/apt/lists/* 48 | 49 | # Create a vapor user and group with /app as its home directory 50 | RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor 51 | 52 | # Switch to the new home directory 53 | WORKDIR /app 54 | 55 | # Copy built executable and any staged resources from builder 56 | COPY --from=build --chown=vapor:vapor /staging /app 57 | 58 | # Ensure all further commands run as the vapor user 59 | USER vapor:vapor 60 | 61 | # Let Docker bind to port 8080 62 | EXPOSE 8080 63 | 64 | # Start the Vapor service when the image is run, default to listening on 8080 in production environment 65 | ENTRYPOINT ["./Run"] 66 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 67 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM swift:5.5-focal as build 5 | 6 | # Install OS updates and, if needed, sqlite3 7 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 8 | && apt-get -q update \ 9 | && apt-get -q dist-upgrade -y \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | # Set up a build area 13 | WORKDIR /build 14 | 15 | # First just resolve dependencies. 16 | # This creates a cached layer that can be reused 17 | # as long as your Package.swift/Package.resolved 18 | # files do not change. 19 | COPY ./Package.* ./ 20 | RUN swift package resolve 21 | 22 | # Copy entire repo into container 23 | COPY . . 24 | 25 | # Build everything, with optimizations 26 | RUN swift build -c release --static-swift-stdlib 27 | 28 | # Switch to the staging area 29 | WORKDIR /staging 30 | 31 | # Copy main executable to staging area 32 | RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./ 33 | 34 | # Copy any resources from the public directory and views directory if the directories exist 35 | # Ensure that by default, neither the directory nor any of its contents are writable. 36 | RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true 37 | RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true 38 | 39 | # ================================ 40 | # Run image 41 | # ================================ 42 | FROM ubuntu:focal 43 | 44 | # Make sure all system packages are up to date. 45 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true && \ 46 | apt-get -q update && apt-get -q dist-upgrade -y && apt-get -q install -y ca-certificates && \ 47 | rm -r /var/lib/apt/lists/* 48 | 49 | # Create a vapor user and group with /app as its home directory 50 | RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor 51 | 52 | # Switch to the new home directory 53 | WORKDIR /app 54 | 55 | # Copy built executable and any staged resources from builder 56 | COPY --from=build --chown=vapor:vapor /staging /app 57 | 58 | # Ensure all further commands run as the vapor user 59 | USER vapor:vapor 60 | 61 | # Let Docker bind to port 8080 62 | EXPOSE 8080 63 | 64 | # Start the Vapor service when the image is run, default to listening on 8080 in production environment 65 | ENTRYPOINT ["./Run"] 66 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 67 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================ 2 | # Build image 3 | # ================================ 4 | FROM swift:5.5-focal as build 5 | 6 | # Install OS updates and, if needed, sqlite3 7 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ 8 | && apt-get -q update \ 9 | && apt-get -q dist-upgrade -y \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | # Set up a build area 13 | WORKDIR /build 14 | 15 | # First just resolve dependencies. 16 | # This creates a cached layer that can be reused 17 | # as long as your Package.swift/Package.resolved 18 | # files do not change. 19 | COPY ./Package.* ./ 20 | RUN swift package resolve 21 | 22 | # Copy entire repo into container 23 | COPY . . 24 | 25 | # Build everything, with optimizations 26 | RUN swift build -c release --static-swift-stdlib 27 | 28 | # Switch to the staging area 29 | WORKDIR /staging 30 | 31 | # Copy main executable to staging area 32 | RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./ 33 | 34 | # Copy any resources from the public directory and views directory if the directories exist 35 | # Ensure that by default, neither the directory nor any of its contents are writable. 36 | RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true 37 | RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true 38 | 39 | # ================================ 40 | # Run image 41 | # ================================ 42 | FROM ubuntu:focal 43 | 44 | # Make sure all system packages are up to date. 45 | RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true && \ 46 | apt-get -q update && apt-get -q dist-upgrade -y && apt-get -q install -y ca-certificates && \ 47 | rm -r /var/lib/apt/lists/* 48 | 49 | # Create a vapor user and group with /app as its home directory 50 | RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor 51 | 52 | # Switch to the new home directory 53 | WORKDIR /app 54 | 55 | # Copy built executable and any staged resources from builder 56 | COPY --from=build --chown=vapor:vapor /staging /app 57 | 58 | # Ensure all further commands run as the vapor user 59 | USER vapor:vapor 60 | 61 | # Let Docker bind to port 8080 62 | EXPOSE 8080 63 | 64 | # Start the Vapor service when the image is run, default to listening on 8080 in production environment 65 | ENTRYPOINT ["./Run"] 66 | CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] 67 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct UserController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let usersRoutes = routes.grouped("api", "users") 8 | usersRoutes.post(use: createHandler) 9 | usersRoutes.get(use: getAllHandler) 10 | usersRoutes.get(":userID", use: getHandler) 11 | usersRoutes.delete(":userID", use: deleteHandler) 12 | usersRoutes.put(":userID", use: updateHandler) 13 | usersRoutes.get(":userID", "reminders", use: getRemindersHandler) 14 | usersRoutes.get("reminders", use: getAllUsersWithRemindersEagerHandler) 15 | } 16 | 17 | func createHandler(req: Request) async throws -> User { 18 | let user = try req.content.decode(User.self) 19 | try await user.save(on: req.db) 20 | return user 21 | } 22 | 23 | func getAllHandler(req: Request) async throws -> [User] { 24 | try await User.query(on: req.db).all() 25 | } 26 | 27 | func getHandler(req: Request) async throws -> User { 28 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 29 | throw Abort(.notFound) 30 | } 31 | return user 32 | } 33 | 34 | func deleteHandler(req: Request) async throws -> HTTPStatus { 35 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 36 | throw Abort(.notFound) 37 | } 38 | try await user.delete(on: req.db) 39 | return .ok 40 | } 41 | 42 | func updateHandler(req: Request) async throws -> User { 43 | let updatedUser = try req.content.decode(User.self) 44 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 45 | throw Abort(.notFound) 46 | } 47 | user.name = updatedUser.name 48 | user.username = updatedUser.username 49 | try await user.save(on: req.db) 50 | return user 51 | } 52 | 53 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 54 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 55 | throw Abort(.notFound) 56 | } 57 | return try await user.$reminders.get(on: req.db) 58 | } 59 | 60 | func getAllUsersWithRemindersEagerHandler(req: Request) async throws -> [User] { 61 | try await User.query(on: req.db).with(\.$reminders).all() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct UserController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let usersRoutes = routes.grouped("api", "users") 8 | usersRoutes.post(use: createHandler) 9 | usersRoutes.get(use: getAllHandler) 10 | usersRoutes.get(":userID", use: getHandler) 11 | usersRoutes.delete(":userID", use: deleteHandler) 12 | usersRoutes.put(":userID", use: updateHandler) 13 | usersRoutes.get(":userID", "reminders", use: getRemindersHandler) 14 | usersRoutes.get("reminders", use: getAllUsersWithRemindersEagerHandler) 15 | } 16 | 17 | func createHandler(req: Request) async throws -> User { 18 | let user = try req.content.decode(User.self) 19 | try await user.save(on: req.db) 20 | return user 21 | } 22 | 23 | func getAllHandler(req: Request) async throws -> [User] { 24 | try await User.query(on: req.db).all() 25 | } 26 | 27 | func getHandler(req: Request) async throws -> User { 28 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 29 | throw Abort(.notFound) 30 | } 31 | return user 32 | } 33 | 34 | func deleteHandler(req: Request) async throws -> HTTPStatus { 35 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 36 | throw Abort(.notFound) 37 | } 38 | try await user.delete(on: req.db) 39 | return .ok 40 | } 41 | 42 | func updateHandler(req: Request) async throws -> User { 43 | let updatedUser = try req.content.decode(User.self) 44 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 45 | throw Abort(.notFound) 46 | } 47 | user.name = updatedUser.name 48 | user.username = updatedUser.username 49 | try await user.save(on: req.db) 50 | return user 51 | } 52 | 53 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 54 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 55 | throw Abort(.notFound) 56 | } 57 | return try await user.$reminders.get(on: req.db) 58 | } 59 | 60 | func getAllUsersWithRemindersEagerHandler(req: Request) async throws -> [User] { 61 | try await User.query(on: req.db).with(\.$reminders).all() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Day1/Morning/Sources/App/Controllers/ReminderController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct RemindersController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let remindersRoutes = routes.grouped("api", "reminders") 8 | remindersRoutes.post(use: createHandler) 9 | remindersRoutes.get(use: getAllHandler) 10 | remindersRoutes.get(":reminderID", use: getHandler) 11 | remindersRoutes.get(":reminderID", "user", use: getUserHandler) 12 | remindersRoutes.post(":reminderID", "categories", ":categoryID", use: addCategoriesHandler) 13 | remindersRoutes.get(":reminderID", "categories", use: getCategoriesHandler) 14 | } 15 | 16 | func createHandler(req: Request) async throws -> Reminder { 17 | let reminderData = try req.content.decode(CreateReminderData.self) 18 | let reminder = Reminder(title: reminderData.title, userID: reminderData.userID) 19 | try await reminder.save(on: req.db) 20 | return reminder 21 | } 22 | 23 | func getAllHandler(req: Request) async throws -> [Reminder] { 24 | try await Reminder.query(on: req.db).all() 25 | } 26 | 27 | func getHandler(req: Request) async throws -> Reminder { 28 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 29 | throw Abort(.notFound) 30 | } 31 | return reminder 32 | } 33 | 34 | func getUserHandler(req: Request) async throws -> User { 35 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 36 | throw Abort(.notFound) 37 | } 38 | return try await reminder.$user.get(on: req.db) 39 | } 40 | 41 | func addCategoriesHandler(req: Request) async throws -> HTTPStatus { 42 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 43 | throw Abort(.notFound) 44 | } 45 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 46 | throw Abort(.notFound) 47 | } 48 | try await reminder.$categories.attach(category, on: req.db) 49 | return .ok 50 | } 51 | 52 | func getCategoriesHandler(req: Request) async throws -> [Category] { 53 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 54 | throw Abort(.notFound) 55 | } 56 | return try await reminder.$categories.get(on: req.db) 57 | } 58 | } 59 | 60 | struct CreateReminderData: Content { 61 | let title: String 62 | let userID: User.IDValue 63 | } 64 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Sources/App/Controllers/ReminderController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct RemindersController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let remindersRoutes = routes.grouped("api", "reminders") 8 | remindersRoutes.post(use: createHandler) 9 | remindersRoutes.get(use: getAllHandler) 10 | remindersRoutes.get(":reminderID", use: getHandler) 11 | remindersRoutes.get(":reminderID", "user", use: getUserHandler) 12 | remindersRoutes.post(":reminderID", "categories", ":categoryID", use: addCategoriesHandler) 13 | remindersRoutes.get(":reminderID", "categories", use: getCategoriesHandler) 14 | } 15 | 16 | func createHandler(req: Request) async throws -> Reminder { 17 | let reminderData = try req.content.decode(CreateReminderData.self) 18 | let reminder = Reminder(title: reminderData.title, userID: reminderData.userID) 19 | try await reminder.save(on: req.db) 20 | return reminder 21 | } 22 | 23 | func getAllHandler(req: Request) async throws -> [Reminder] { 24 | try await Reminder.query(on: req.db).all() 25 | } 26 | 27 | func getHandler(req: Request) async throws -> Reminder { 28 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 29 | throw Abort(.notFound) 30 | } 31 | return reminder 32 | } 33 | 34 | func getUserHandler(req: Request) async throws -> User { 35 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 36 | throw Abort(.notFound) 37 | } 38 | return try await reminder.$user.get(on: req.db) 39 | } 40 | 41 | func addCategoriesHandler(req: Request) async throws -> HTTPStatus { 42 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 43 | throw Abort(.notFound) 44 | } 45 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 46 | throw Abort(.notFound) 47 | } 48 | try await reminder.$categories.attach(category, on: req.db) 49 | return .ok 50 | } 51 | 52 | func getCategoriesHandler(req: Request) async throws -> [Category] { 53 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 54 | throw Abort(.notFound) 55 | } 56 | return try await reminder.$categories.get(on: req.db) 57 | } 58 | } 59 | 60 | struct CreateReminderData: Content { 61 | let title: String 62 | let userID: User.IDValue 63 | } 64 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Controllers/ReminderController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct RemindersController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let remindersRoutes = routes.grouped("api", "reminders") 8 | remindersRoutes.get(use: getAllHandler) 9 | remindersRoutes.get(":reminderID", use: getHandler) 10 | remindersRoutes.get(":reminderID", "user", use: getUserHandler) 11 | remindersRoutes.get(":reminderID", "categories", use: getCategoriesHandler) 12 | 13 | let protectedRoutes = remindersRoutes.grouped(Token.authenticator(), User.guardMiddleware()) 14 | protectedRoutes.post(use: createHandler) 15 | protectedRoutes.post(":reminderID", "categories", ":categoryID", use: addCategoriesHandler) 16 | } 17 | 18 | func createHandler(req: Request) async throws -> Reminder { 19 | let reminderData = try req.content.decode(CreateReminderData.self) 20 | let user = try req.auth.require(User.self) 21 | let reminder = try Reminder(title: reminderData.title, userID: user.requireID()) 22 | try await reminder.save(on: req.db) 23 | return reminder 24 | } 25 | 26 | func getAllHandler(req: Request) async throws -> [Reminder] { 27 | try await Reminder.query(on: req.db).all() 28 | } 29 | 30 | func getHandler(req: Request) async throws -> Reminder { 31 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 32 | throw Abort(.notFound) 33 | } 34 | return reminder 35 | } 36 | 37 | func getUserHandler(req: Request) async throws -> User { 38 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 39 | throw Abort(.notFound) 40 | } 41 | return try await reminder.$user.get(on: req.db) 42 | } 43 | 44 | func addCategoriesHandler(req: Request) async throws -> HTTPStatus { 45 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 46 | throw Abort(.notFound) 47 | } 48 | guard let category = try await Category.find(req.parameters.get("categoryID"), on: req.db) else { 49 | throw Abort(.notFound) 50 | } 51 | try await reminder.$categories.attach(category, on: req.db) 52 | return .ok 53 | } 54 | 55 | func getCategoriesHandler(req: Request) async throws -> [Category] { 56 | guard let reminder = try await Reminder.find(req.parameters.get("reminderID"), on: req.db) else { 57 | throw Abort(.notFound) 58 | } 59 | return try await reminder.$categories.get(on: req.db) 60 | } 61 | } 62 | 63 | struct CreateReminderData: Content { 64 | let title: String 65 | } 66 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Sources/App/Controllers/UserController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | struct UserController: RouteCollection { 5 | 6 | func boot(routes: RoutesBuilder) throws { 7 | let usersRoutes = routes.grouped("api", "users") 8 | usersRoutes.post(use: createHandler) 9 | usersRoutes.get(use: getAllHandler) 10 | usersRoutes.get(":userID", use: getHandler) 11 | usersRoutes.delete(":userID", use: deleteHandler) 12 | usersRoutes.put(":userID", use: updateHandler) 13 | usersRoutes.get(":userID", "reminders", use: getRemindersHandler) 14 | usersRoutes.get("reminders", use: getAllUsersWithRemindersEagerHandler) 15 | 16 | let httpBasicAuthRoutes = usersRoutes.grouped(User.authenticator()) 17 | httpBasicAuthRoutes.post("login", use: loginHandler) 18 | } 19 | 20 | func createHandler(req: Request) async throws -> User { 21 | let user = try req.content.decode(User.self) 22 | user.passwordHash = try Bcrypt.hash(user.passwordHash) 23 | try await user.save(on: req.db) 24 | return user 25 | } 26 | 27 | func getAllHandler(req: Request) async throws -> [User] { 28 | try await User.query(on: req.db).all() 29 | } 30 | 31 | func getHandler(req: Request) async throws -> User { 32 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 33 | throw Abort(.notFound) 34 | } 35 | return user 36 | } 37 | 38 | func deleteHandler(req: Request) async throws -> HTTPStatus { 39 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 40 | throw Abort(.notFound) 41 | } 42 | try await user.delete(on: req.db) 43 | return .ok 44 | } 45 | 46 | func updateHandler(req: Request) async throws -> User { 47 | let updatedUser = try req.content.decode(User.self) 48 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 49 | throw Abort(.notFound) 50 | } 51 | user.name = updatedUser.name 52 | user.username = updatedUser.username 53 | user.passwordHash = try Bcrypt.hash(updatedUser.passwordHash) 54 | try await user.save(on: req.db) 55 | return user 56 | } 57 | 58 | func getRemindersHandler(req: Request) async throws -> [Reminder] { 59 | guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else { 60 | throw Abort(.notFound) 61 | } 62 | return try await user.$reminders.get(on: req.db) 63 | } 64 | 65 | func getAllUsersWithRemindersEagerHandler(req: Request) async throws -> [User] { 66 | try await User.query(on: req.db).with(\.$reminders).all() 67 | } 68 | 69 | func loginHandler(_ req: Request) async throws -> Token { 70 | let user = try req.auth.require(User.self) 71 | let token = try user.generateToken() 72 | try await token.save(on: req.db) 73 | return token 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Day1/Afternoon/starter/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "async-http-client", 6 | "repositoryURL": "https://github.com/swift-server/async-http-client.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "60fa3dcfc52d09ed0411e9c4bc99928bf63656bd", 10 | "version": "1.24.2" 11 | } 12 | }, 13 | { 14 | "package": "async-kit", 15 | "repositoryURL": "https://github.com/vapor/async-kit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", 19 | "version": "1.20.0" 20 | } 21 | }, 22 | { 23 | "package": "console-kit", 24 | "repositoryURL": "https://github.com/vapor/console-kit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", 28 | "version": "4.15.2" 29 | } 30 | }, 31 | { 32 | "package": "fluent", 33 | "repositoryURL": "https://github.com/vapor/fluent.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "223b27d04ab2b51c25503c9922eecbcdf6c12f89", 37 | "version": "4.12.0" 38 | } 39 | }, 40 | { 41 | "package": "fluent-kit", 42 | "repositoryURL": "https://github.com/vapor/fluent-kit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "153c495198ad29508e9c89baf87cbfda798c5338", 46 | "version": "1.50.1" 47 | } 48 | }, 49 | { 50 | "package": "fluent-sqlite-driver", 51 | "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "6e3a5ff7f2cb733771a6bd71dd3a491cce79f24d", 55 | "version": "4.8.0" 56 | } 57 | }, 58 | { 59 | "package": "multipart-kit", 60 | "repositoryURL": "https://github.com/vapor/multipart-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "3498e60218e6003894ff95192d756e238c01f44e", 64 | "version": "4.7.1" 65 | } 66 | }, 67 | { 68 | "package": "routing-kit", 69 | "repositoryURL": "https://github.com/vapor/routing-kit.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "8c9a227476555c55837e569be71944e02a056b72", 73 | "version": "4.9.1" 74 | } 75 | }, 76 | { 77 | "package": "sql-kit", 78 | "repositoryURL": "https://github.com/vapor/sql-kit.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "e0b35ff07601465dd9f3af19a1c23083acaae3bd", 82 | "version": "3.32.0" 83 | } 84 | }, 85 | { 86 | "package": "sqlite-kit", 87 | "repositoryURL": "https://github.com/vapor/sqlite-kit.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "f35a863ecc2da5d563b836a9a696b148b0f4169f", 91 | "version": "4.5.2" 92 | } 93 | }, 94 | { 95 | "package": "sqlite-nio", 96 | "repositoryURL": "https://github.com/vapor/sqlite-nio.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "256c6b816e3bb3764ab98639c7f04d10cec88cff", 100 | "version": "1.11.0" 101 | } 102 | }, 103 | { 104 | "package": "swift-algorithms", 105 | "repositoryURL": "https://github.com/apple/swift-algorithms.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "f6919dfc309e7f1b56224378b11e28bab5bccc42", 109 | "version": "1.2.0" 110 | } 111 | }, 112 | { 113 | "package": "swift-asn1", 114 | "repositoryURL": "https://github.com/apple/swift-asn1.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", 118 | "version": "1.3.1" 119 | } 120 | }, 121 | { 122 | "package": "swift-atomics", 123 | "repositoryURL": "https://github.com/apple/swift-atomics.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "cd142fd2f64be2100422d658e7411e39489da985", 127 | "version": "1.2.0" 128 | } 129 | }, 130 | { 131 | "package": "swift-collections", 132 | "repositoryURL": "https://github.com/apple/swift-collections.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", 136 | "version": "1.1.4" 137 | } 138 | }, 139 | { 140 | "package": "swift-crypto", 141 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "ff0f781cf7c6a22d52957e50b104f5768b50c779", 145 | "version": "3.10.0" 146 | } 147 | }, 148 | { 149 | "package": "swift-distributed-tracing", 150 | "repositoryURL": "https://github.com/apple/swift-distributed-tracing.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", 154 | "version": "1.2.0" 155 | } 156 | }, 157 | { 158 | "package": "swift-http-types", 159 | "repositoryURL": "https://github.com/apple/swift-http-types", 160 | "state": { 161 | "branch": null, 162 | "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", 163 | "version": "1.3.1" 164 | } 165 | }, 166 | { 167 | "package": "swift-log", 168 | "repositoryURL": "https://github.com/apple/swift-log.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", 172 | "version": "1.6.2" 173 | } 174 | }, 175 | { 176 | "package": "swift-metrics", 177 | "repositoryURL": "https://github.com/apple/swift-metrics.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", 181 | "version": "2.5.1" 182 | } 183 | }, 184 | { 185 | "package": "swift-nio", 186 | "repositoryURL": "https://github.com/apple/swift-nio.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "dff45738d84a53dbc8ee899c306b3a7227f54f89", 190 | "version": "2.80.0" 191 | } 192 | }, 193 | { 194 | "package": "swift-nio-extras", 195 | "repositoryURL": "https://github.com/apple/swift-nio-extras.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "2e9746cfc57554f70b650b021b6ae4738abef3e6", 199 | "version": "1.24.1" 200 | } 201 | }, 202 | { 203 | "package": "swift-nio-http2", 204 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", 208 | "version": "1.35.0" 209 | } 210 | }, 211 | { 212 | "package": "swift-nio-ssl", 213 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "c7e95421334b1068490b5d41314a50e70bab23d1", 217 | "version": "2.29.0" 218 | } 219 | }, 220 | { 221 | "package": "swift-nio-transport-services", 222 | "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", 226 | "version": "1.23.0" 227 | } 228 | }, 229 | { 230 | "package": "swift-numerics", 231 | "repositoryURL": "https://github.com/apple/swift-numerics.git", 232 | "state": { 233 | "branch": null, 234 | "revision": "0a5bc04095a675662cf24757cc0640aa2204253b", 235 | "version": "1.0.2" 236 | } 237 | }, 238 | { 239 | "package": "swift-service-context", 240 | "repositoryURL": "https://github.com/apple/swift-service-context.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "0c62c5b4601d6c125050b5c3a97f20cce881d32b", 244 | "version": "1.1.0" 245 | } 246 | }, 247 | { 248 | "package": "swift-system", 249 | "repositoryURL": "https://github.com/apple/swift-system.git", 250 | "state": { 251 | "branch": null, 252 | "revision": "c8a44d836fe7913603e246acab7c528c2e780168", 253 | "version": "1.4.0" 254 | } 255 | }, 256 | { 257 | "package": "vapor", 258 | "repositoryURL": "https://github.com/vapor/vapor.git", 259 | "state": { 260 | "branch": null, 261 | "revision": "8589cb562feab069f2563bdcdeb8f9608a07a2c7", 262 | "version": "4.112.0" 263 | } 264 | }, 265 | { 266 | "package": "websocket-kit", 267 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 268 | "state": { 269 | "branch": null, 270 | "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", 271 | "version": "2.15.0" 272 | } 273 | } 274 | ] 275 | }, 276 | "version": 1 277 | } 278 | -------------------------------------------------------------------------------- /Day1/Afternoon/final/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "async-http-client", 6 | "repositoryURL": "https://github.com/swift-server/async-http-client.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f38c2fea867d230941f9b45fea6eda819d78fbb4", 10 | "version": "1.24.1" 11 | } 12 | }, 13 | { 14 | "package": "async-kit", 15 | "repositoryURL": "https://github.com/vapor/async-kit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", 19 | "version": "1.20.0" 20 | } 21 | }, 22 | { 23 | "package": "console-kit", 24 | "repositoryURL": "https://github.com/vapor/console-kit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", 28 | "version": "4.15.2" 29 | } 30 | }, 31 | { 32 | "package": "fluent", 33 | "repositoryURL": "https://github.com/vapor/fluent.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "223b27d04ab2b51c25503c9922eecbcdf6c12f89", 37 | "version": "4.12.0" 38 | } 39 | }, 40 | { 41 | "package": "fluent-kit", 42 | "repositoryURL": "https://github.com/vapor/fluent-kit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "153c495198ad29508e9c89baf87cbfda798c5338", 46 | "version": "1.50.1" 47 | } 48 | }, 49 | { 50 | "package": "fluent-sqlite-driver", 51 | "repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "6e3a5ff7f2cb733771a6bd71dd3a491cce79f24d", 55 | "version": "4.8.0" 56 | } 57 | }, 58 | { 59 | "package": "leaf", 60 | "repositoryURL": "https://github.com/vapor/leaf.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "bf48d2423c00292b5937c60166c7db99705cae47", 64 | "version": "4.4.1" 65 | } 66 | }, 67 | { 68 | "package": "leaf-kit", 69 | "repositoryURL": "https://github.com/vapor/leaf-kit.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "902c51288a6a1c3f19d9e33d1aebee066364c0e6", 73 | "version": "1.12.0" 74 | } 75 | }, 76 | { 77 | "package": "multipart-kit", 78 | "repositoryURL": "https://github.com/vapor/multipart-kit.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "3498e60218e6003894ff95192d756e238c01f44e", 82 | "version": "4.7.1" 83 | } 84 | }, 85 | { 86 | "package": "routing-kit", 87 | "repositoryURL": "https://github.com/vapor/routing-kit.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "8c9a227476555c55837e569be71944e02a056b72", 91 | "version": "4.9.1" 92 | } 93 | }, 94 | { 95 | "package": "sql-kit", 96 | "repositoryURL": "https://github.com/vapor/sql-kit.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "e0b35ff07601465dd9f3af19a1c23083acaae3bd", 100 | "version": "3.32.0" 101 | } 102 | }, 103 | { 104 | "package": "sqlite-kit", 105 | "repositoryURL": "https://github.com/vapor/sqlite-kit.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "f35a863ecc2da5d563b836a9a696b148b0f4169f", 109 | "version": "4.5.2" 110 | } 111 | }, 112 | { 113 | "package": "sqlite-nio", 114 | "repositoryURL": "https://github.com/vapor/sqlite-nio.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "256c6b816e3bb3764ab98639c7f04d10cec88cff", 118 | "version": "1.11.0" 119 | } 120 | }, 121 | { 122 | "package": "swift-algorithms", 123 | "repositoryURL": "https://github.com/apple/swift-algorithms.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "f6919dfc309e7f1b56224378b11e28bab5bccc42", 127 | "version": "1.2.0" 128 | } 129 | }, 130 | { 131 | "package": "swift-asn1", 132 | "repositoryURL": "https://github.com/apple/swift-asn1.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", 136 | "version": "1.3.1" 137 | } 138 | }, 139 | { 140 | "package": "swift-atomics", 141 | "repositoryURL": "https://github.com/apple/swift-atomics.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "cd142fd2f64be2100422d658e7411e39489da985", 145 | "version": "1.2.0" 146 | } 147 | }, 148 | { 149 | "package": "swift-collections", 150 | "repositoryURL": "https://github.com/apple/swift-collections.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", 154 | "version": "1.1.4" 155 | } 156 | }, 157 | { 158 | "package": "swift-crypto", 159 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "ff0f781cf7c6a22d52957e50b104f5768b50c779", 163 | "version": "3.10.0" 164 | } 165 | }, 166 | { 167 | "package": "swift-distributed-tracing", 168 | "repositoryURL": "https://github.com/apple/swift-distributed-tracing.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", 172 | "version": "1.2.0" 173 | } 174 | }, 175 | { 176 | "package": "swift-http-types", 177 | "repositoryURL": "https://github.com/apple/swift-http-types", 178 | "state": { 179 | "branch": null, 180 | "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", 181 | "version": "1.3.1" 182 | } 183 | }, 184 | { 185 | "package": "swift-log", 186 | "repositoryURL": "https://github.com/apple/swift-log.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", 190 | "version": "1.6.2" 191 | } 192 | }, 193 | { 194 | "package": "swift-metrics", 195 | "repositoryURL": "https://github.com/apple/swift-metrics.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", 199 | "version": "2.5.1" 200 | } 201 | }, 202 | { 203 | "package": "swift-nio", 204 | "repositoryURL": "https://github.com/apple/swift-nio.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "dff45738d84a53dbc8ee899c306b3a7227f54f89", 208 | "version": "2.80.0" 209 | } 210 | }, 211 | { 212 | "package": "swift-nio-extras", 213 | "repositoryURL": "https://github.com/apple/swift-nio-extras.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "2e9746cfc57554f70b650b021b6ae4738abef3e6", 217 | "version": "1.24.1" 218 | } 219 | }, 220 | { 221 | "package": "swift-nio-http2", 222 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", 226 | "version": "1.35.0" 227 | } 228 | }, 229 | { 230 | "package": "swift-nio-ssl", 231 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 232 | "state": { 233 | "branch": null, 234 | "revision": "c7e95421334b1068490b5d41314a50e70bab23d1", 235 | "version": "2.29.0" 236 | } 237 | }, 238 | { 239 | "package": "swift-nio-transport-services", 240 | "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", 244 | "version": "1.23.0" 245 | } 246 | }, 247 | { 248 | "package": "swift-numerics", 249 | "repositoryURL": "https://github.com/apple/swift-numerics", 250 | "state": { 251 | "branch": null, 252 | "revision": "0a5bc04095a675662cf24757cc0640aa2204253b", 253 | "version": "1.0.2" 254 | } 255 | }, 256 | { 257 | "package": "swift-service-context", 258 | "repositoryURL": "https://github.com/apple/swift-service-context.git", 259 | "state": { 260 | "branch": null, 261 | "revision": "0c62c5b4601d6c125050b5c3a97f20cce881d32b", 262 | "version": "1.1.0" 263 | } 264 | }, 265 | { 266 | "package": "swift-system", 267 | "repositoryURL": "https://github.com/apple/swift-system.git", 268 | "state": { 269 | "branch": null, 270 | "revision": "c8a44d836fe7913603e246acab7c528c2e780168", 271 | "version": "1.4.0" 272 | } 273 | }, 274 | { 275 | "package": "vapor", 276 | "repositoryURL": "https://github.com/vapor/vapor.git", 277 | "state": { 278 | "branch": null, 279 | "revision": "8589cb562feab069f2563bdcdeb8f9608a07a2c7", 280 | "version": "4.112.0" 281 | } 282 | }, 283 | { 284 | "package": "websocket-kit", 285 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 286 | "state": { 287 | "branch": null, 288 | "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", 289 | "version": "2.15.0" 290 | } 291 | } 292 | ] 293 | }, 294 | "version": 1 295 | } 296 | -------------------------------------------------------------------------------- /Day1/Morning/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "async-http-client", 6 | "repositoryURL": "https://github.com/swift-server/async-http-client.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "60fa3dcfc52d09ed0411e9c4bc99928bf63656bd", 10 | "version": "1.24.2" 11 | } 12 | }, 13 | { 14 | "package": "async-kit", 15 | "repositoryURL": "https://github.com/vapor/async-kit.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", 19 | "version": "1.20.0" 20 | } 21 | }, 22 | { 23 | "package": "console-kit", 24 | "repositoryURL": "https://github.com/vapor/console-kit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", 28 | "version": "4.15.2" 29 | } 30 | }, 31 | { 32 | "package": "fluent", 33 | "repositoryURL": "https://github.com/vapor/fluent.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "223b27d04ab2b51c25503c9922eecbcdf6c12f89", 37 | "version": "4.12.0" 38 | } 39 | }, 40 | { 41 | "package": "fluent-kit", 42 | "repositoryURL": "https://github.com/vapor/fluent-kit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "153c495198ad29508e9c89baf87cbfda798c5338", 46 | "version": "1.50.1" 47 | } 48 | }, 49 | { 50 | "package": "fluent-postgres-driver", 51 | "repositoryURL": "https://github.com/vapor/fluent-postgres-driver.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "fd57101e426d3edf66a32ba63a7d0b8ced4d7499", 55 | "version": "2.10.0" 56 | } 57 | }, 58 | { 59 | "package": "multipart-kit", 60 | "repositoryURL": "https://github.com/vapor/multipart-kit.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "3498e60218e6003894ff95192d756e238c01f44e", 64 | "version": "4.7.1" 65 | } 66 | }, 67 | { 68 | "package": "postgres-kit", 69 | "repositoryURL": "https://github.com/vapor/postgres-kit.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "0b72fa83b1023c4b82072e4049a3db6c29781fff", 73 | "version": "2.13.5" 74 | } 75 | }, 76 | { 77 | "package": "postgres-nio", 78 | "repositoryURL": "https://github.com/vapor/postgres-nio.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "fd0e415a705c490499f983639b04f491a2ed9d99", 82 | "version": "1.23.0" 83 | } 84 | }, 85 | { 86 | "package": "routing-kit", 87 | "repositoryURL": "https://github.com/vapor/routing-kit.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "8c9a227476555c55837e569be71944e02a056b72", 91 | "version": "4.9.1" 92 | } 93 | }, 94 | { 95 | "package": "sql-kit", 96 | "repositoryURL": "https://github.com/vapor/sql-kit.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "e0b35ff07601465dd9f3af19a1c23083acaae3bd", 100 | "version": "3.32.0" 101 | } 102 | }, 103 | { 104 | "package": "swift-algorithms", 105 | "repositoryURL": "https://github.com/apple/swift-algorithms.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "f6919dfc309e7f1b56224378b11e28bab5bccc42", 109 | "version": "1.2.0" 110 | } 111 | }, 112 | { 113 | "package": "swift-asn1", 114 | "repositoryURL": "https://github.com/apple/swift-asn1.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", 118 | "version": "1.3.1" 119 | } 120 | }, 121 | { 122 | "package": "swift-async-algorithms", 123 | "repositoryURL": "https://github.com/apple/swift-async-algorithms.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "4c3ea81f81f0a25d0470188459c6d4bf20cf2f97", 127 | "version": "1.0.3" 128 | } 129 | }, 130 | { 131 | "package": "swift-atomics", 132 | "repositoryURL": "https://github.com/apple/swift-atomics.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "cd142fd2f64be2100422d658e7411e39489da985", 136 | "version": "1.2.0" 137 | } 138 | }, 139 | { 140 | "package": "swift-collections", 141 | "repositoryURL": "https://github.com/apple/swift-collections.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "671108c96644956dddcd89dd59c203dcdb36cec7", 145 | "version": "1.1.4" 146 | } 147 | }, 148 | { 149 | "package": "swift-crypto", 150 | "repositoryURL": "https://github.com/apple/swift-crypto.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "ff0f781cf7c6a22d52957e50b104f5768b50c779", 154 | "version": "3.10.0" 155 | } 156 | }, 157 | { 158 | "package": "swift-distributed-tracing", 159 | "repositoryURL": "https://github.com/apple/swift-distributed-tracing.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "a64a0abc2530f767af15dd88dda7f64d5f1ff9de", 163 | "version": "1.2.0" 164 | } 165 | }, 166 | { 167 | "package": "swift-http-types", 168 | "repositoryURL": "https://github.com/apple/swift-http-types", 169 | "state": { 170 | "branch": null, 171 | "revision": "ef18d829e8b92d731ad27bb81583edd2094d1ce3", 172 | "version": "1.3.1" 173 | } 174 | }, 175 | { 176 | "package": "swift-log", 177 | "repositoryURL": "https://github.com/apple/swift-log.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", 181 | "version": "1.6.2" 182 | } 183 | }, 184 | { 185 | "package": "swift-metrics", 186 | "repositoryURL": "https://github.com/apple/swift-metrics.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "5e63558d12e0267782019f5dadfcae83a7d06e09", 190 | "version": "2.5.1" 191 | } 192 | }, 193 | { 194 | "package": "swift-nio", 195 | "repositoryURL": "https://github.com/apple/swift-nio.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "dff45738d84a53dbc8ee899c306b3a7227f54f89", 199 | "version": "2.80.0" 200 | } 201 | }, 202 | { 203 | "package": "swift-nio-extras", 204 | "repositoryURL": "https://github.com/apple/swift-nio-extras.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "2e9746cfc57554f70b650b021b6ae4738abef3e6", 208 | "version": "1.24.1" 209 | } 210 | }, 211 | { 212 | "package": "swift-nio-http2", 213 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "170f4ca06b6a9c57b811293cebcb96e81b661310", 217 | "version": "1.35.0" 218 | } 219 | }, 220 | { 221 | "package": "swift-nio-ssl", 222 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "c7e95421334b1068490b5d41314a50e70bab23d1", 226 | "version": "2.29.0" 227 | } 228 | }, 229 | { 230 | "package": "swift-nio-transport-services", 231 | "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", 232 | "state": { 233 | "branch": null, 234 | "revision": "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", 235 | "version": "1.23.0" 236 | } 237 | }, 238 | { 239 | "package": "swift-numerics", 240 | "repositoryURL": "https://github.com/apple/swift-numerics.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "0a5bc04095a675662cf24757cc0640aa2204253b", 244 | "version": "1.0.2" 245 | } 246 | }, 247 | { 248 | "package": "swift-service-context", 249 | "repositoryURL": "https://github.com/apple/swift-service-context.git", 250 | "state": { 251 | "branch": null, 252 | "revision": "0c62c5b4601d6c125050b5c3a97f20cce881d32b", 253 | "version": "1.1.0" 254 | } 255 | }, 256 | { 257 | "package": "swift-service-lifecycle", 258 | "repositoryURL": "https://github.com/swift-server/swift-service-lifecycle.git", 259 | "state": { 260 | "branch": null, 261 | "revision": "c2e97cf6f81510f2d6b4a69453861db65d478560", 262 | "version": "2.6.3" 263 | } 264 | }, 265 | { 266 | "package": "swift-system", 267 | "repositoryURL": "https://github.com/apple/swift-system.git", 268 | "state": { 269 | "branch": null, 270 | "revision": "c8a44d836fe7913603e246acab7c528c2e780168", 271 | "version": "1.4.0" 272 | } 273 | }, 274 | { 275 | "package": "vapor", 276 | "repositoryURL": "https://github.com/vapor/vapor.git", 277 | "state": { 278 | "branch": null, 279 | "revision": "8589cb562feab069f2563bdcdeb8f9608a07a2c7", 280 | "version": "4.112.0" 281 | } 282 | }, 283 | { 284 | "package": "websocket-kit", 285 | "repositoryURL": "https://github.com/vapor/websocket-kit.git", 286 | "state": { 287 | "branch": null, 288 | "revision": "4232d34efa49f633ba61afde365d3896fc7f8740", 289 | "version": "2.15.0" 290 | } 291 | } 292 | ] 293 | }, 294 | "version": 1 295 | } 296 | --------------------------------------------------------------------------------