main d5c447a5fefe cached
1555 files
1.3 MB
357.6k tokens
14 symbols
1 requests
Download .txt
Showing preview only (1,765K chars total). Download the full file or copy to clipboard to get everything.
Repository: tib/practical-server-side-swift
Branch: main
Commit: d5c447a5fefe
Files: 1555
Total size: 1.3 MB

Directory structure:
gitextract_ovv508se/

├── .gitignore
├── Changelog.md
├── Chapter 01/
│   └── .gitkeep
├── Chapter 02/
│   ├── SPM/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   └── Sources/
│   │       └── main.swift
│   └── VaporToolbox/
│       ├── .dockerignore
│       ├── .gitignore
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   └── .gitkeep
│       ├── README.md
│       ├── Sources/
│       │   └── myProject/
│       │       ├── Controllers/
│       │       │   └── .gitkeep
│       │       ├── configure.swift
│       │       ├── entrypoint.swift
│       │       └── routes.swift
│       ├── Tests/
│       │   └── myProjectTests/
│       │       └── myProjectTests.swift
│       └── docker-compose.yml
├── Chapter 03/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   └── myProject/
│       │       ├── Middlewares/
│       │       │   └── ExtendPathMiddleware.swift
│       │       ├── Modules/
│       │       │   ├── Blog/
│       │       │   │   ├── BlogPost.swift
│       │       │   │   ├── BlogRouter.swift
│       │       │   │   ├── Controllers/
│       │       │   │   │   └── BlogFrontendController.swift
│       │       │   │   └── Templates/
│       │       │   │       ├── Contexts/
│       │       │   │       │   ├── BlogPostContext.swift
│       │       │   │       │   └── BlogPostsContext.swift
│       │       │   │       └── Html/
│       │       │   │           ├── BlogPostTemplate.swift
│       │       │   │           └── BlogPostsTemplate.swift
│       │       │   └── Web/
│       │       │       ├── Controllers/
│       │       │       │   └── WebFrontendController.swift
│       │       │       ├── Templates/
│       │       │       │   ├── Contexts/
│       │       │       │   │   ├── WebHomeContext.swift
│       │       │       │   │   ├── WebIndexContext.swift
│       │       │       │   │   └── WebLinkContext.swift
│       │       │       │   └── Html/
│       │       │       │       ├── WebHomeTemplate.swift
│       │       │       │       ├── WebIndexTemplate.swift
│       │       │       │       └── WebLinkTemplate.swift
│       │       │       └── WebRouter.swift
│       │       ├── Template/
│       │       │   ├── Request+Template.swift
│       │       │   ├── TemplateRenderer.swift
│       │       │   └── TemplateRepresentable.swift
│       │       ├── configure.swift
│       │       ├── entrypoint.swift
│       │       └── routes.swift
│       ├── Tests/
│       │   └── myProjectTests/
│       │       └── myProjectTests.swift
│       └── docker-compose.yml
├── Chapter 04/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 05/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 06/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   └── InputField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   └── LabelContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           └── LabelTemplate.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 07/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   └── InputField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   └── LabelContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           └── LabelTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 08/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   ├── FormImageInput.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── HiddenFieldContext.swift
│       │   │   │   │       │   ├── ImageFieldContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   ├── LabelContext.swift
│       │   │   │   │       │   ├── OptionContext.swift
│       │   │   │   │       │   ├── SelectFieldContext.swift
│       │   │   │   │       │   └── TextareaFieldContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── HiddenFieldTemplate.swift
│       │   │   │   │           ├── ImageFieldTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           ├── LabelTemplate.swift
│       │   │   │   │           ├── SelectFieldTemplate.swift
│       │   │   │   │           └── TextareaFieldTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 09/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   ├── FormImageInput.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── HiddenFieldContext.swift
│       │   │   │   │       │   ├── ImageFieldContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   ├── LabelContext.swift
│       │   │   │   │       │   ├── OptionContext.swift
│       │   │   │   │       │   ├── SelectFieldContext.swift
│       │   │   │   │       │   └── TextareaFieldContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── HiddenFieldTemplate.swift
│       │   │   │   │           ├── ImageFieldTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           ├── LabelTemplate.swift
│       │   │   │   │           ├── SelectFieldTemplate.swift
│       │   │   │   │           └── TextareaFieldTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── AdminFrontendController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   └── AdminIndexContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           └── AdminIndexTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 10/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   └── ModelController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 11/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   └── ModelController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 12/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   └── Controllers/
│       │   │   │   │       ├── ApiController.swift
│       │   │   │   │       ├── ApiCreateController.swift
│       │   │   │   │       ├── ApiDeleteController.swift
│       │   │   │   │       ├── ApiDetailController.swift
│       │   │   │   │       ├── ApiListController.swift
│       │   │   │   │       ├── ApiPatchController.swift
│       │   │   │   │       └── ApiUpdateController.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 13/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 14/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
├── Chapter 15/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── Hooks/
│       │   │   │   │   ├── Application+HookStorage.swift
│       │   │   │   │   ├── Async/
│       │   │   │   │   │   ├── Application+AsyncHooks.swift
│       │   │   │   │   │   ├── AsyncAnyHookFunction.swift
│       │   │   │   │   │   ├── AsyncHookFunction.swift
│       │   │   │   │   │   ├── HookStorage+AsyncHooks.swift
│       │   │   │   │   │   └── Request+AsyncHooks.swift
│       │   │   │   │   ├── HookArguments.swift
│       │   │   │   │   ├── HookFunctionPointer.swift
│       │   │   │   │   ├── HookStorage.swift
│       │   │   │   │   └── Sync/
│       │   │   │   │       ├── AnyHookFunction.swift
│       │   │   │   │       ├── Application+Hooks.swift
│       │   │   │   │       ├── HookFunction.swift
│       │   │   │   │       ├── HookStorage+Hooks.swift
│       │   │   │   │       └── Request+Hooks.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── ApiRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogAdminWidgetTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
├── Chapter 16/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface+PathComponent.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── Hooks/
│       │   │   │   │   ├── Application+HookStorage.swift
│       │   │   │   │   ├── Async/
│       │   │   │   │   │   ├── Application+AsyncHooks.swift
│       │   │   │   │   │   ├── AsyncAnyHookFunction.swift
│       │   │   │   │   │   ├── AsyncHookFunction.swift
│       │   │   │   │   │   ├── HookStorage+AsyncHooks.swift
│       │   │   │   │   │   └── Request+AsyncHooks.swift
│       │   │   │   │   ├── HookArguments.swift
│       │   │   │   │   ├── HookFunctionPointer.swift
│       │   │   │   │   ├── HookStorage.swift
│       │   │   │   │   └── Sync/
│       │   │   │   │       ├── AnyHookFunction.swift
│       │   │   │   │       ├── Application+Hooks.swift
│       │   │   │   │       ├── HookFunction.swift
│       │   │   │   │       ├── HookStorage+Hooks.swift
│       │   │   │   │       └── Request+Hooks.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── ApiRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogAdminWidgetTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   ├── AppApi/
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   └── ApiModuleInterface.swift
│       │   │   └── Modules/
│       │   │       ├── Blog/
│       │   │       │   ├── Blog.swift
│       │   │       │   ├── BlogCategory.swift
│       │   │       │   └── BlogPost.swift
│       │   │       └── User/
│       │   │           ├── User.swift
│       │   │           ├── UserAccount.swift
│       │   │           └── UserToken.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   ├── AppApiTests/
│       │   │   └── AppApiTests.swift
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
.build
.swiftpm
xcuserdata
MyProject.xcodeproj
Packages
DerivedData/
db.sqlite
Package.resolved


================================================
FILE: Changelog.md
================================================
## Changelog - Practical Server Side Swift

Don't forget to check the sample code repository on [GitHub](https://github.com/tib/practical-server-side-swift).

### Third edition - v1.5.0

- Xcode 14.3 support
- Swift 5.8 support
- Vapor 4.70.0+
- SwiftHtml 1.7.0
- Reworked some parts of the book
- Added some helpful notes & commands
- Fixed some grammar mistakes & typos
- Thanks to Ferenc (reviewer) & Michael (editor)

### Third edition - v1.4.0

- Swift async / await support
- Xcode 13 support
- Dropped Leaf / Tau 
- SwiftHtml DSL templates
- Latest Vapor 4 version

### Second edition - v1.3.1

- Content updated to support Vapor 4.34 & Leaf Tau
- Fixed trailing slashes in URL paths
- Updated Vapor & Leaf Tau dependency
- Fixed some grammar mistakes


### Second edition - v1.3.0

- Content updated to support Vapor 4.34 & Leaf Tau
- Chapters from 10 until 16 are refactored
- Simplified templates thanks to Leaf Tau
- SPM tools v5.3 support
- Updated Swift Package dependencies
- Fixed some logical issues in code samples
- It is now easier to follow the chapters
- Pagination support for admin lists
- Smaller fixes and improvements
- New grammar mistakes & typos


### First edition - v1.2.0

- Content updated to support Vapor 4.14
- Fixed unowned reference issue
- Fixed Blog migration issues using a dedicated seed
- Sample .env.development file for Chapter 12
- Fixed some grammar mistakes


### First edition - v1.1.0

- Better document format with screenshots
- Fixed some grammar mistakes
- Introduced FILE name comment blocks


### First edition - v1.0.0

- First release


### Early Bird edition v1.0.0-beta-6

- Chapter 16


### Early Bird edition v1.0.0-beta-5

- Chapter 15


### Early Bird edition v1.0.0-beta-4

- Chapter 13
- Chapter 14

### Early Bird edition v1.0.0-beta-3.1

- Added epub format


### Early Bird edition v1.0.0-beta-3

- Chapter 12


### Early Bird edition v1.0.0-beta-2

- Chapter 11 


### Early Bird edition v1.0.0-beta-1

- Chapter 1-10



================================================
FILE: Chapter 01/.gitkeep
================================================


================================================
FILE: Chapter 02/SPM/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/


================================================
FILE: Chapter 02/SPM/Package.swift
================================================
// swift-tools-version:6.1
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v12),
    ],
    products: [
        .executable(name: "myProject", targets: ["myProject"]),
    ],
    dependencies: [
        .package(
            url: "https://github.com/vapor/vapor",
            from: "4.115.0"
        ),
    ],
    targets: [
        .executableTarget(name: "myProject", dependencies: [
            .product(name: "Vapor", package: "vapor")
        ]),
    ]
)


================================================
FILE: Chapter 02/SPM/README.md
================================================
# myProject

A description of this package.


================================================
FILE: Chapter 02/SPM/Sources/main.swift
================================================
import Vapor

let env = try Environment.detect()
let app = try await Application.make(env)

do {
    app.get { req in "Hello Vapor!" }
    try await app.execute()
}
catch {
    try? await app.asyncShutdown()
    throw error
}
try await app.asyncShutdown()


================================================
FILE: Chapter 02/VaporToolbox/.dockerignore
================================================
.build/
.swiftpm/


================================================
FILE: Chapter 02/VaporToolbox/.gitignore
================================================
Packages
.build
xcuserdata
*.xcodeproj
DerivedData/
.DS_Store
db.sqlite
.swiftpm
.env
.env.*
!.env.example

================================================
FILE: Chapter 02/VaporToolbox/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 02/VaporToolbox/Package.swift
================================================
// swift-tools-version:6.0
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v13),
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor", from: "4.115.0"),
        .package(url: "https://github.com/apple/swift-nio", from: "2.84.0"),
    ],
    targets: [
        .executableTarget(
            name: "myProject",
            dependencies: [
                .product(name: "Vapor", package: "vapor"),
                .product(name: "NIOCore", package: "swift-nio"),
                .product(name: "NIOPosix", package: "swift-nio"),
            ],
            swiftSettings: swiftSettings
        ),
        .testTarget(
            name: "myProjectTests",
            dependencies: [
                .target(name: "myProject"),
                .product(name: "VaporTesting", package: "vapor"),
            ],
            swiftSettings: swiftSettings
        )
    ]
)

var swiftSettings: [SwiftSetting] { [
    .enableUpcomingFeature("ExistentialAny"),
] }


================================================
FILE: Chapter 02/VaporToolbox/Public/.gitkeep
================================================


================================================
FILE: Chapter 02/VaporToolbox/README.md
================================================
# myProject

💧 A project built with the Vapor web framework.

## Getting Started

To build the project using the Swift Package Manager, run the following command in the terminal from the root of the project:
```bash
swift build
```

To run the project and start the server, use the following command:
```bash
swift run
```

To execute tests, use the following command:
```bash
swift test
```

### See more

- [Vapor Website](https://vapor.codes)
- [Vapor Documentation](https://docs.vapor.codes)
- [Vapor GitHub](https://github.com/vapor)
- [Vapor Community](https://github.com/vapor-community)


================================================
FILE: Chapter 02/VaporToolbox/Sources/myProject/Controllers/.gitkeep
================================================


================================================
FILE: Chapter 02/VaporToolbox/Sources/myProject/configure.swift
================================================
import Vapor

// configures your application
public func configure(_ app: Application) async throws {
    // uncomment to serve files from /Public folder
    // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

    // register routes
    try routes(app)
}


================================================
FILE: Chapter 02/VaporToolbox/Sources/myProject/entrypoint.swift
================================================
import Vapor
import Logging
import NIOCore
import NIOPosix

@main
enum Entrypoint {

    static func main() async throws {
        var env = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)
        
        let app = try await Application.make(env)

        do {
            try await configure(app)
            try await app.execute()
        }
        catch {
            app.logger.report(error: error)
            try? await app.asyncShutdown()
            throw error
        }
        try await app.asyncShutdown()
    }
}


================================================
FILE: Chapter 02/VaporToolbox/Sources/myProject/routes.swift
================================================
import Vapor

func routes(_ app: Application) throws {
    app.get { req async in
        "It works!"
    }

    app.get("hello") { req async -> String in
        "Hello, world!"
    }
}


================================================
FILE: Chapter 02/VaporToolbox/Tests/myProjectTests/myProjectTests.swift
================================================
@testable import myProject
import VaporTesting
import Testing

@Suite("App Tests")
struct myProjectTests {

    @Test("Test Hello World Route")
    func helloWorld() async throws {
        try await withApp(configure: configure) { app in
            try await app.testing().test(.GET, "hello", afterResponse: { res async in
                #expect(res.status == .ok)
                #expect(res.body.string == "Hello, world!")
            })
        }
    }
}


================================================
FILE: Chapter 02/VaporToolbox/docker-compose.yml
================================================
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker compose build
#      Start app: docker compose up app
#       Stop all: docker compose down
#

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  
services:
  app:
    image: myproject:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 03/myProject/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 03/myProject/Package.swift
================================================
// swift-tools-version:6.0
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v13),
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor", from: "4.115.0"),
        .package(url: "https://github.com/binarybirds/swift-html", from: "1.7.0"),
    ],
    targets: [
        .executableTarget(
            name: "myProject",
            dependencies: [
                .product(name: "Vapor", package: "vapor"),
                .product(name: "SwiftHtml", package: "swift-html"),
                .product(name: "SwiftSvg", package: "swift-html"),
            ],
            swiftSettings: swiftSettings
        ),
        .testTarget(
            name: "myProjectTests",
            dependencies: [
                .target(name: "myProject"),
                .product(name: "VaporTesting", package: "vapor"),
            ],
            swiftSettings: swiftSettings
        )
    ]
)

var swiftSettings: [SwiftSetting] { [
    .enableUpcomingFeature("ExistentialAny"),
] }


================================================
FILE: Chapter 03/myProject/Public/css/web.css
================================================
#blog h2 {
    margin: 0.5rem 0;
}


================================================
FILE: Chapter 03/myProject/Public/js/web.js
================================================
function about() {
    alert("myPage\n\nversion 1.0.0");
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Middlewares/ExtendPathMiddleware.swift
================================================
import Vapor

struct ExtendPathMiddleware: AsyncMiddleware {

    func respond(
        to req: Request,
        chainingTo next: any AsyncResponder
    ) async throws -> Response {
        if !req.url.path.hasSuffix("/") && !req.url.path.contains(".") {
            return req.redirect(
                to: req.url.path + "/",
                redirectType: .permanent
            )
        }
        return try await next.respond(
            to: req
        )
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/BlogPost.swift
================================================
import Foundation

struct BlogPost: Codable {
    let title: String
    let slug: String
    let image: String
    let excerpt: String
    let date: Date
    let category: String?
    let content: String
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/BlogRouter.swift
================================================
import Vapor

struct BlogRouter: RouteCollection {
    
    let controller = BlogFrontendController()
    
    func boot(
        routes: any RoutesBuilder
    ) throws {
        routes.get("blog", use: controller.blogView)
        routes.get(.anything, use: controller.postView)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/Controllers/BlogFrontendController.swift
================================================
import Vapor

struct BlogFrontendController {
    
    var posts: [BlogPost] = {
        stride(from: 1, to: 9, by: 1).map { index in
            BlogPost(
                title: "Sample post #\(index)",
                slug: "sample-post-\(index)",
                image: "/img/posts/\(String(format: "%02d", index + 1)).jpg",
                excerpt: "Lorem ipsum",
                date: Date().addingTimeInterval(-Double.random(in: 0...(86400 * 60))),
                category: Bool.random() ? "Sample category" : nil,
                content: "Lorem ipsum dolor sit amet."
            )
        }.sorted() { $0.date > $1.date }
    }()
    
    func blogView(
        req: Request
    ) throws -> Response {
        let ctx = BlogPostsContext(
            icon: "🔥",
            title: "Blog",
            message: "Hot news and stories about everything.",
            posts: posts
        )
        return req.templates.renderHtml(
            BlogPostsTemplate(ctx)
        )
    }
    
    func postView(
        req: Request
    ) throws -> Response {
        let slug = req.url.path.trimmingCharacters(
            in: .init(charactersIn: "/")
        )
        guard let post = posts.first(where: { $0.slug == slug }) else {
            return req.redirect(to: "/")
        }
        let ctx = BlogPostContext(post: post)
        return req.templates.renderHtml(
            BlogPostTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Contexts/BlogPostContext.swift
================================================
struct BlogPostContext {
    let post: BlogPost
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Contexts/BlogPostsContext.swift
================================================
struct BlogPostsContext {
    let icon: String
    let title: String
    let message: String
    let posts: [BlogPost]
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Html/BlogPostTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostTemplate: TemplateRepresentable {

    var context: BlogPostContext
    
    init(
        _ context: BlogPostContext
    ) {
        self.context = context
    }
    
    var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        formatter.timeStyle = .short
        return formatter
    }()

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.post.title)
        ) {
            Div {
                Section {
                    P(dateFormatter.string(from: context.post.date))
                    H1(context.post.title)
                    P(context.post.excerpt)
                }
                .class(["lead", "container"])
                
                Img(src: context.post.image, alt: context.post.title)
                
                Article {
                    Text(context.post.content)
                }
                .class("container")
            }
            .id("post")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Html/BlogPostsTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostsTemplate: TemplateRepresentable {
    
    var context: BlogPostsContext
    
    init(
        _ context: BlogPostsContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                Div {
                    for post in context.posts {
                        Article {
                            A {
                                Img(src: post.image, alt: post.title)
                                H2(post.title)
                                P(post.excerpt)
                            }
                            .href("/\(post.slug)/")
                        }
                    }
                }
                .class("grid-221")
            }
            .id("blog")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Controllers/WebFrontendController.swift
================================================
import Vapor

struct WebFrontendController {
    
    func homeView(req: Request) throws -> Response {
        let ctx = WebHomeContext(
            icon: "👋",
            title: "Home",
            message: "Hi there, welcome to my page.",
            paragraphs: [
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
                "Nisi ut aliquip ex ea commodo consequat.",
            ],
            link: .init(
                label: "Read my blog →",
                url: "/blog/"
            )
        )
        
        return req.templates.renderHtml(
            WebHomeTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebHomeContext.swift
================================================
struct WebHomeContext {
    let icon: String
    let title: String
    let message: String
    let paragraphs: [String]
    let link: WebLinkContext
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebIndexContext.swift
================================================
public struct WebIndexContext {
    
    public let title: String
    
    public init(
        title: String
    ) {
        self.title = title
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebLinkContext.swift
================================================
public struct WebLinkContext {
    
    public let label: String
    public let url: String
    
    public init(
        label: String,
        url: String
    ) {
        self.label = label
        self.url = url
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebHomeTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebHomeTemplate: TemplateRepresentable {

    var context: WebHomeContext
    
    init(
        _ context: WebHomeContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                for paragraph in context.paragraphs {
                    P(paragraph)
                }

                WebLinkTemplate(context.link).render(req)
            }
            .id("home")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebIndexTemplate.swift
================================================
import Vapor
import SwiftHtml
import SwiftSvg

extension Svg {
    
    static func menuIcon() -> Svg {
        Svg {
            Line(x1: 3, y1: 12, x2: 21, y2: 12)
            Line(x1: 3, y1: 6, x2: 21, y2: 6)
            Line(x1: 3, y1: 18, x2: 21, y2: 18)
        }
        .width(24)
        .height(24)
        .viewBox(minX: 0, minY: 0, width: 24, height: 24)
        .fill("none")
        .stroke("currentColor")
        .strokeWidth(2)
        .strokeLinecap("round")
        .strokeLinejoin("round")
    }
}

public struct WebIndexTemplate: TemplateRepresentable {

    public var context: WebIndexContext
    var body: Tag

    public init(
        _ context: WebIndexContext,
        @TagBuilder _ builder: () -> Tag
    ) {
        self.context = context
        self.body = builder()
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Html {
            Head {
                Meta()
                    .charset("utf-8")
                Meta()
                    .name(.viewport)
                    .content("width=device-width, initial-scale=1")

                Link(rel: .shortcutIcon)
                    .href("/img/favicon.ico")
                    .type("image/x-icon")
                Link(rel: .stylesheet)
                    .href("/css/peacock.min.css")
                Link(rel: .stylesheet)
                    .href("/css/web.css")
                
                Title(context.title)
            }
            Body {
                Header {
                    Div {
                        A {
                            Img(src: "/img/logo.png", alt: "Logo")
                        }
                        .id("site-logo")
                        .href("/")
                        
                        Nav {
                            Input()
                                .type(.checkbox)
                                .id("primary-menu-button")
                                .name("menu-button")
                                .class("menu-button")
                            Label {
                                Svg.menuIcon()
                            }
                            .for("primary-menu-button")
                            Div {
                                A("Home")
                                    .href("/")
                                    .class("selected", req.url.path == "/")
                                A("Blog")
                                    .href("/blog/")
                                    .class("selected", req.url.path == "/blog/")
                                A("About")
                                    .href("#")
                                    .onClick("javascript:about();")
                            }
                            .class("menu-items")
                        }
                        .id("primary-menu")
                    }
                    .id("navigation")
                }
                
                Main {
                    body
                }

                Footer {
                    Section {
                        P {
                            Text("This site is powered by ")
                            A("Swift")
                                .href("https://swift.org")
                                .target(.blank)
                            Text(" & ")
                            A("Vapor")
                                .href("https://vapor.codes")
                                .target(.blank)
                            Text(".")
                        }
                        P("myPage &copy; 2020-2022")
                    }
                }
                
                Script()
                    .type(.javascript)
                    .src("/js/web.js")
                
            }
        }
        .lang("en-US")
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebLinkTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebLinkTemplate: TemplateRepresentable {

    var context: WebLinkContext
    
    init(
        _ context: WebLinkContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        A(context.label)
            .href(context.url)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Modules/Web/WebRouter.swift
================================================
import Vapor

struct WebRouter: RouteCollection {
    
    let frontendController = WebFrontendController()

    func boot(
        routes: any RoutesBuilder
    ) throws {
        routes.get(use: frontendController.homeView)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Template/Request+Template.swift
================================================
import Vapor

public extension Request {
    var templates: TemplateRenderer { .init(self) }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Template/TemplateRenderer.swift
================================================
import Vapor
import SwiftHtml

public struct TemplateRenderer {
    
    var req: Request
    
    init(_ req: Request) {
        self.req = req
    }

    public func renderHtml(
        _ template: any TemplateRepresentable,
        minify: Bool = false,
        indent: Int = 4
    ) -> Response {
        let doc = Document(.html) {
            template.render(req)
        }
        let body = DocumentRenderer(
            minify: minify,
            indent: indent
        )
        .render(doc)
        return Response(
            status: .ok,
            headers: [
                "content-type": "text/html"
            ],
            body: .init(string: body)
        )
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/Template/TemplateRepresentable.swift
================================================
import Vapor
import SwiftSgml

public protocol TemplateRepresentable {
    
    @TagBuilder
    func render(_ req: Request) -> Tag
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/configure.swift
================================================
import Vapor

public func configure(
    _ app: Application
) async throws {

    app.middleware.use(
        FileMiddleware(
            publicDirectory: app.directory.publicDirectory
        )
    )

    app.middleware.use(ExtendPathMiddleware())

    let routers: [any RouteCollection] = [
        WebRouter(),
        BlogRouter(),
    ]
    for router in routers {
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/entrypoint.swift
================================================
import Vapor
import Logging

@main
enum Entrypoint {

    static func main() async throws {
        var env = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)
        
        let app = try await Application.make(env)

        do {
            try await configure(app)
            try await app.execute()
        }
        catch {
            app.logger.report(error: error)
            try? await app.asyncShutdown()
            throw error
        }
        try await app.asyncShutdown()
    }
}


================================================
FILE: Chapter 03/myProject/Sources/myProject/routes.swift
================================================
import Vapor
import SwiftHtml

func routes(_ app: Application) throws {

    app.routes.get { req -> Response in
        req.templates.renderHtml(
            WebIndexTemplate(
                WebIndexContext(
                    title: "Home"
                )
            ) {
                P("Hi there, welcome to my page!")
            }
        )
    }
}


================================================
FILE: Chapter 03/myProject/Tests/myProjectTests/myProjectTests.swift
================================================
@testable import myProject
import VaporTesting
import Testing

@Suite("App Tests")
struct myProjectTests {

    @Test("Test Hello World Route")
    func helloWorld() async throws {
        try await withApp(configure: configure) { app in
            try await app.testing().test(.GET, "hello", afterResponse: { res async in
                #expect(res.status == .ok)
                #expect(res.body.string == "Hello, world!")
            })
        }
    }
}


================================================
FILE: Chapter 03/myProject/docker-compose.yml
================================================
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker compose build
#      Start app: docker compose up app
#       Stop all: docker compose down
#

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  
services:
  app:
    image: myproject:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 04/myProject/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 04/myProject/Package.swift
================================================
// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v12)
    ],
    dependencies: [
        .package(
            url: "https://github.com/vapor/vapor",
            from: "4.70.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent",
            from: "4.4.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent-sqlite-driver",
            from: "4.1.0"
        ),
        .package(
            url: "https://github.com/binarybirds/swift-html",
            from: "1.7.0"
        ),
    ],
    targets: [
        .target(name: "App", dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            .product(name: "SwiftHtml", package: "swift-html"),
            .product(name: "SwiftSvg", package: "swift-html"),
        ]),
        .executableTarget(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)


================================================
FILE: Chapter 04/myProject/Public/css/web.css
================================================
#blog h2 {
    margin: 0.5rem 0;
}


================================================
FILE: Chapter 04/myProject/Public/js/web.js
================================================
function about() {
    alert("myPage\n\nversion 1.0.0");
}


================================================
FILE: Chapter 04/myProject/Sources/App/Framework/DatabaseModelInterface.swift
================================================
import Vapor
import Fluent

public protocol DatabaseModelInterface: Fluent.Model
    where Self.IDValue == UUID
{    
    associatedtype Module: ModuleInterface

    static var identifier: String { get }
}

public extension DatabaseModelInterface {

    static var schema: String {
        Module.identifier + "_" + identifier
    }
    
    static var identifier: String {
        String(describing: self)
            .dropFirst(Module.identifier.count)
            .dropLast(5)
            .lowercased() + "s"
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Framework/ModuleInterface.swift
================================================
import Vapor

public protocol ModuleInterface {
    
    static var identifier: String { get }

    func boot(_ app: Application) throws
}

public extension ModuleInterface {

    func boot(_ app: Application) throws {}

    static var identifier: String {
        String(describing: self).dropLast(6).lowercased()
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift
================================================
import Vapor

struct ExtendPathMiddleware: AsyncMiddleware {

    func respond(
        to req: Request,
        chainingTo next: AsyncResponder
    ) async throws -> Response {
        if !req.url.path.hasSuffix("/") && !req.url.path.contains(".") {
            return req.redirect(
                to: req.url.path + "/",
                redirectType: .permanent
            )
        }
        return try await next.respond(
            to: req
        )
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/BlogModule.swift
================================================
import Vapor

struct BlogModule: ModuleInterface {

    let router = BlogRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(BlogMigrations.v1())
        app.migrations.add(BlogMigrations.seed())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/BlogRouter.swift
================================================
import Vapor

struct BlogRouter: RouteCollection {
    
    let controller = BlogFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("blog", use: controller.blogView)
        routes.get(.anything, use: controller.postView)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift
================================================
import Vapor
import Fluent

struct BlogFrontendController {
    
    func blogView(req: Request) async throws -> Response {
        let postModels = try await BlogPostModel
            .query(on: req.db)
            .sort(\.$date, .descending)
            .all()

        let posts = try postModels.map {
            Blog.Post.List(
                id: try $0.requireID(),
                title: $0.title,
                slug: $0.slug,
                image: $0.imageKey,
                excerpt: $0.excerpt,
                date: $0.date
            )
        }

        let ctx = BlogPostsContext(
            icon: "🔥",
            title: "Blog",
            message: "Hot news and stories about everything.",
            posts: posts)

        return req.templates.renderHtml(
            BlogPostsTemplate(ctx)
        )
    }

    func postView(req: Request) async throws -> Response {
        let slug = req.url.path.trimmingCharacters(
            in: .init(charactersIn: "/")
        )
        guard
            let post = try await BlogPostModel
                .query(on: req.db)
                .filter(\.$slug == slug)
                .with(\.$category)
                .first()
        else {
            return req.redirect(to: "/")
        }
        let ctx = BlogPostContext(
            post: Blog.Post.Detail(
                id: post.id!,
                title: post.title,
                slug: post.slug,
                image: post.imageKey,
                excerpt: post.excerpt,
                date: post.date,
                category: .init(
                    id: post.category.id!,
                    title: post.category.title
                ),
                content: post.content
            )
        )

        return req.templates.renderHtml(
            BlogPostTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift
================================================
import Foundation
import Fluent

enum BlogMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(BlogCategoryModel.schema)
                .id()
                .field(BlogCategoryModel.FieldKeys.v1.title, .string, .required)
                .create()
            
            try await db.schema(BlogPostModel.schema)
                .id()
                .field(BlogPostModel.FieldKeys.v1.title, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.slug, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.imageKey, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.excerpt, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.date, .datetime, .required)
                .field(BlogPostModel.FieldKeys.v1.content, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.categoryId, .uuid)
                .foreignKey(
                    BlogPostModel.FieldKeys.v1.categoryId,
                    references: BlogCategoryModel.schema, .id,
                    onDelete: DatabaseSchema.ForeignKeyAction.setNull,
                    onUpdate: .cascade
                )
                .unique(on: BlogPostModel.FieldKeys.v1.slug)
                .create()
        }
        
        func revert(on db: Database) async throws  {
            try await db.schema(BlogCategoryModel.schema).delete()
            try await db.schema(BlogPostModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            
            let categories = (1...4).map { index in
                BlogCategoryModel(title: "Sample category #\(index)")
            }
            try await categories.create(on: db)
            
            try await (1...9).map { index in
                BlogPostModel(
                    id: nil, title: "Sample post #\(index)",
                    slug: "sample-post-\(index)",
                    imageKey: "/img/posts/\(String(format: "%02d", index + 1)).jpg",
                    excerpt: "Lorem ipsum",
                    date: Date().addingTimeInterval(-Double.random(in: 0...(86400 * 60))),
                    content: "Lorem ipsum dolor sit amet.",
                    categoryId: categories[Int.random(in: 0..<categories.count)].id!
                )
            }
            .create(on: db)
        }
        
        func revert(on db: Database) async throws {
            try await BlogPostModel.query(on: db).delete()
            try await BlogCategoryModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift
================================================
import Vapor
import Fluent

final class BlogCategoryModel: DatabaseModelInterface {
    typealias Module = BlogModule

    static let identifier = "categories"
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
        }
    }
    
    @ID() var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Children(for: \.$category)
    var posts: [BlogPostModel]
    
    init() { }
    
    init(
        id: UUID? = nil,
        title: String
    ) {
        self.id = id
        self.title = title
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift
================================================
import Vapor
import Fluent

final class BlogPostModel: DatabaseModelInterface {
    typealias Module = BlogModule
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
            static var slug: FieldKey { "slug" }
            static var imageKey: FieldKey { "image_key" }
            static var excerpt: FieldKey { "excerpt" }
            static var date: FieldKey { "date" }
            static var content: FieldKey { "content" }
            static var categoryId: FieldKey { "category_id" }
        }
    }

    @ID()
    var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Field(key: FieldKeys.v1.slug)
    var slug: String
    
    @Field(key: FieldKeys.v1.imageKey)
    var imageKey: String
    
    @Field(key: FieldKeys.v1.excerpt)
    var excerpt: String
    
    @Field(key: FieldKeys.v1.date)
    var date: Date
    
    @Field(key: FieldKeys.v1.content)
    var content: String
    
    @Parent(key: FieldKeys.v1.categoryId)
    var category: BlogCategoryModel

    init() { }

    init(
        id: UUID? = nil,
        title: String,
        slug: String,
        imageKey: String,
        excerpt: String,
        date: Date,
        content: String,
        categoryId: UUID)
    {
        self.id = id
        self.title = title
        self.slug = slug
        self.imageKey = imageKey
        self.excerpt = excerpt
        self.date = date
        self.content = content
        $category.id = categoryId
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Objects/Blog.swift
================================================
enum Blog {
    
    enum Post {
        
    }

    enum Category {
        
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift
================================================
import Foundation

extension Blog.Category {
    
    struct List: Codable {
        let id: UUID
        let title: String
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift
================================================
import Foundation

extension Blog.Post {
    
    struct List: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
    }
    
    struct Detail: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
        let category: Blog.Category.List
        let content: String
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift
================================================
struct BlogPostContext {
    let post: Blog.Post.Detail
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift
================================================
struct BlogPostsContext {
    let icon: String
    let title: String
    let message: String
    let posts: [Blog.Post.List]
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostTemplate: TemplateRepresentable {

    var context: BlogPostContext
    
    init(
        _ context: BlogPostContext
    ) {
        self.context = context
    }
    
    var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        formatter.timeStyle = .short
        return formatter
    }()

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.post.title)
        ) {
            Div {
                Section {
                    P(dateFormatter.string(from: context.post.date))
                    H1(context.post.title)
                    P(context.post.excerpt)
                }
                .class(["lead", "container"])
                
                Img(src: context.post.image, alt: context.post.title)
                
                Article {
                    Text(context.post.content)
                }
                .class("container")
            }
            .id("post")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostsTemplate: TemplateRepresentable {
    
    var context: BlogPostsContext
    
    init(
        _ context: BlogPostsContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                Div {
                    for post in context.posts {
                        Article {
                            A {
                                Img(src: post.image, alt: post.title)
                                H2(post.title)
                                P(post.excerpt)
                            }
                            .href("/\(post.slug)/")
                        }
                    }
                }
                .class("grid-221")
            }
            .id("blog")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift
================================================
import Vapor

struct WebFrontendController {
    
    func homeView(req: Request) throws -> Response {
        let ctx = WebHomeContext(
            icon: "👋",
            title: "Home",
            message: "Hi there, welcome to my page.",
            paragraphs: [
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
                "Nisi ut aliquip ex ea commodo consequat.",
            ],
            link: .init(
                label: "Read my blog →",
                url: "/blog/"
            )
        )
        
        return req.templates.renderHtml(
            WebHomeTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift
================================================
struct WebHomeContext {
    let icon: String
    let title: String
    let message: String
    let paragraphs: [String]
    let link: WebLinkContext
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift
================================================
public struct WebIndexContext {
    
    public let title: String
    
    public init(
        title: String
    ) {
        self.title = title
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift
================================================
public struct WebLinkContext {
    
    public let label: String
    public let url: String
    
    public init(
        label: String,
        url: String
    ) {
        self.label = label
        self.url = url
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebHomeTemplate: TemplateRepresentable {

    var context: WebHomeContext
    
    init(
        _ context: WebHomeContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                for paragraph in context.paragraphs {
                    P(paragraph)
                }

                WebLinkTemplate(context.link).render(req)
            }
            .id("home")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift
================================================
import Vapor
import SwiftHtml
import SwiftSvg

extension Svg {
    
    static func menuIcon() -> Svg {
        Svg {
            Line(x1: 3, y1: 12, x2: 21, y2: 12)
            Line(x1: 3, y1: 6, x2: 21, y2: 6)
            Line(x1: 3, y1: 18, x2: 21, y2: 18)
        }
        .width(24)
        .height(24)
        .viewBox(minX: 0, minY: 0, width: 24, height: 24)
        .fill("none")
        .stroke("currentColor")
        .strokeWidth(2)
        .strokeLinecap("round")
        .strokeLinejoin("round")
    }
}

public struct WebIndexTemplate: TemplateRepresentable {

    public var context: WebIndexContext
    var body: Tag

    public init(
        _ context: WebIndexContext,
        @TagBuilder _ builder: () -> Tag
    ) {
        self.context = context
        self.body = builder()
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Html {
            Head {
                Meta()
                    .charset("utf-8")
                Meta()
                    .name(.viewport)
                    .content("width=device-width, initial-scale=1")

                Link(rel: .shortcutIcon)
                    .href("/img/favicon.ico")
                    .type("image/x-icon")
                Link(rel: .stylesheet)
                    .href("https://cdn.jsdelivr.net/gh/feathercms/feather-core@1.0.0-beta.44/feather.min.css")
                Link(rel: .stylesheet)
                    .href("/css/web.css")
                
                Title(context.title)
            }
            Body {
                Header {
                    Div {
                        A {
                            Img(src: "/img/logo.png", alt: "Logo")
                        }
                        .id("site-logo")
                        .href("/")
                        
                        Nav {
                            Input()
                                .type(.checkbox)
                                .id("primary-menu-button")
                                .name("menu-button")
                                .class("menu-button")
                            Label {
                                Svg.menuIcon()
                            }
                            .for("primary-menu-button")
                            Div {
                                A("Home")
                                    .href("/")
                                    .class("selected", req.url.path == "/")
                                A("Blog")
                                    .href("/blog/")
                                    .class("selected", req.url.path == "/blog/")
                                A("About")
                                    .href("#")
                                    .onClick("javascript:about();")
                            }
                            .class("menu-items")
                        }
                        .id("primary-menu")
                    }
                    .id("navigation")
                }
                
                Main {
                    body
                }

                Footer {
                    Section {
                        P {
                            Text("This site is powered by ")
                            A("Swift")
                                .href("https://swift.org")
                                .target(.blank)
                            Text(" & ")
                            A("Vapor")
                                .href("https://vapor.codes")
                                .target(.blank)
                            Text(".")
                        }
                        P("myPage &copy; 2020-2022")
                    }
                }
                
                Script()
                    .type(.javascript)
                    .src("/js/web.js")
                
            }
        }
        .lang("en-US")
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebLinkTemplate: TemplateRepresentable {

    var context: WebLinkContext
    
    init(
        _ context: WebLinkContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        A(context.label)
            .href(context.url)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/WebModule.swift
================================================
import Vapor

struct WebModule: ModuleInterface {

    let router = WebRouter()

    func boot(_ app: Application) throws {
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Modules/Web/WebRouter.swift
================================================
import Vapor

struct WebRouter: RouteCollection {
    
    let frontendController = WebFrontendController()

    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get(use: frontendController.homeView)
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Template/Request+Template.swift
================================================
import Vapor

public extension Request {
    var templates: TemplateRenderer { .init(self) }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Template/TemplateRenderer.swift
================================================
import Vapor
import SwiftHtml

public struct TemplateRenderer {
    
    var req: Request
    
    init(_ req: Request) {
        self.req = req
    }

    public func renderHtml(
        _ template: TemplateRepresentable,
        minify: Bool = false,
        indent: Int = 4
    ) -> Response {
        let doc = Document(.html) {
            template.render(req)
        }
        let body = DocumentRenderer(
            minify: minify,
            indent: indent
        )
        .render(doc)
        return Response(
            status: .ok,
            headers: [
                "content-type": "text/html"
            ],
            body: .init(string: body)
        )
    }
}


================================================
FILE: Chapter 04/myProject/Sources/App/Template/TemplateRepresentable.swift
================================================
import Vapor
import SwiftSgml

public protocol TemplateRepresentable {
    
    @TagBuilder
    func render(_ req: Request) -> Tag
}


================================================
FILE: Chapter 04/myProject/Sources/App/configure.swift
================================================
import Vapor
import Fluent
import FluentSQLiteDriver

public func configure(
    _ app: Application
) throws {

    let dbPath = app.directory.resourcesDirectory + "db.sqlite"
    app.databases.use(.sqlite(.file(dbPath)), as: .sqlite)
    
    app.middleware.use(
        FileMiddleware(
            publicDirectory: app.directory.publicDirectory
        )
    )

    app.middleware.use(ExtendPathMiddleware())

    let modules: [ModuleInterface] = [
        WebModule(),
        BlogModule(),
    ]
    for module in modules {
        try module.boot(app)
    }
    
    try app.autoMigrate().wait()
}


================================================
FILE: Chapter 04/myProject/Sources/App/routes.swift
================================================
import Vapor
import SwiftHtml

func routes(_ app: Application) throws {

    app.routes.get { req -> Response in
        req.templates.renderHtml(
            WebIndexTemplate(
                WebIndexContext(
                    title: "Home"
                )
            ) {
                P("Hi there, welcome to my page!")
            }
        )
    }
}


================================================
FILE: Chapter 04/myProject/Sources/Run/main.swift
================================================
import App
import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try app.run()


================================================
FILE: Chapter 04/myProject/Tests/AppTests/AppTests.swift
================================================
@testable import App
import XCTVapor

final class AppTests: XCTestCase {
    func testHelloWorld() throws {
        let app = Application(.testing)
        defer { app.shutdown() }
        try configure(app)

        try app.test(.GET, "hello", afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            XCTAssertEqual(res.body.string, "Hello, world!")
        })
    }
}


================================================
FILE: Chapter 04/myProject/docker-compose.yml
================================================
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker compose build
#      Start app: docker compose up app
#       Stop all: docker compose down
#

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  
services:
  app:
    image: myproject:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 05/myProject/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 05/myProject/Package.swift
================================================
// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v12)
    ],
    dependencies: [
        .package(
            url: "https://github.com/vapor/vapor",
            from: "4.70.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent",
            from: "4.4.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent-sqlite-driver",
            from: "4.1.0"
        ),
        .package(
            url: "https://github.com/binarybirds/swift-html",
            from: "1.7.0"
        ),
    ],
    targets: [
        .target(name: "App", dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            .product(name: "SwiftHtml", package: "swift-html"),
            .product(name: "SwiftSvg", package: "swift-html"),
        ]),
        .executableTarget(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)


================================================
FILE: Chapter 05/myProject/Public/css/web.css
================================================
#blog h2 {
    margin: 0.5rem 0;
}


================================================
FILE: Chapter 05/myProject/Public/js/web.js
================================================
function about() {
    alert("myPage\n\nversion 1.0.0");
}


================================================
FILE: Chapter 05/myProject/Sources/App/Framework/AuthenticatedUser.swift
================================================
import Vapor

public struct AuthenticatedUser {

    public let id: UUID
    public let email: String
    
    public init(
        id: UUID,
        email: String
    ) {
        self.id = id
        self.email = email
    }
}

extension AuthenticatedUser: SessionAuthenticatable {
    public var sessionID: UUID { id }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Framework/DatabaseModelInterface.swift
================================================
import Vapor
import Fluent

public protocol DatabaseModelInterface: Fluent.Model
    where Self.IDValue == UUID
{    
    associatedtype Module: ModuleInterface

    static var identifier: String { get }
}

public extension DatabaseModelInterface {

    static var schema: String {
        Module.identifier + "_" + identifier
    }
    
    static var identifier: String {
        String(describing: self)
            .dropFirst(Module.identifier.count)
            .dropLast(5)
            .lowercased() + "s"
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Framework/ModuleInterface.swift
================================================
import Vapor

public protocol ModuleInterface {
    
    static var identifier: String { get }

    func boot(_ app: Application) throws
}

public extension ModuleInterface {

    func boot(_ app: Application) throws {}

    static var identifier: String {
        String(describing: self).dropLast(6).lowercased()
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift
================================================
import Vapor

struct ExtendPathMiddleware: AsyncMiddleware {

    func respond(
        to req: Request,
        chainingTo next: AsyncResponder
    ) async throws -> Response {
        if !req.url.path.hasSuffix("/") && !req.url.path.contains(".") {
            return req.redirect(
                to: req.url.path + "/",
                redirectType: .permanent
            )
        }
        return try await next.respond(
            to: req
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/BlogModule.swift
================================================
import Vapor

struct BlogModule: ModuleInterface {

    let router = BlogRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(BlogMigrations.v1())
        app.migrations.add(BlogMigrations.seed())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/BlogRouter.swift
================================================
import Vapor

struct BlogRouter: RouteCollection {
    
    let controller = BlogFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("blog", use: controller.blogView)
        routes.get(.anything, use: controller.postView)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift
================================================
import Vapor
import Fluent

struct BlogFrontendController {
    
    func blogView(req: Request) async throws -> Response {
        let postModels = try await BlogPostModel
            .query(on: req.db)
            .sort(\.$date, .descending)
            .all()

        let posts = try postModels.map {
            Blog.Post.List(
                id: try $0.requireID(),
                title: $0.title,
                slug: $0.slug,
                image: $0.imageKey,
                excerpt: $0.excerpt,
                date: $0.date
            )
        }

        let ctx = BlogPostsContext(
            icon: "🔥",
            title: "Blog",
            message: "Hot news and stories about everything.",
            posts: posts)

        return req.templates.renderHtml(
            BlogPostsTemplate(ctx)
        )
    }

    func postView(req: Request) async throws -> Response {
        let slug = req.url.path.trimmingCharacters(
            in: .init(charactersIn: "/")
        )
        guard
            let post = try await BlogPostModel
                .query(on: req.db)
                .filter(\.$slug == slug)
                .with(\.$category)
                .first()
        else {
            return req.redirect(to: "/")
        }
        let ctx = BlogPostContext(
            post: Blog.Post.Detail(
                id: post.id!,
                title: post.title,
                slug: post.slug,
                image: post.imageKey,
                excerpt: post.excerpt,
                date: post.date,
                category: .init(
                    id: post.category.id!,
                    title: post.category.title
                ),
                content: post.content
            )
        )

        return req.templates.renderHtml(
            BlogPostTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift
================================================
import Foundation
import Fluent

enum BlogMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(BlogCategoryModel.schema)
                .id()
                .field(BlogCategoryModel.FieldKeys.v1.title, .string, .required)
                .create()
            
            try await db.schema(BlogPostModel.schema)
                .id()
                .field(BlogPostModel.FieldKeys.v1.title, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.slug, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.imageKey, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.excerpt, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.date, .datetime, .required)
                .field(BlogPostModel.FieldKeys.v1.content, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.categoryId, .uuid)
                .foreignKey(
                    BlogPostModel.FieldKeys.v1.categoryId,
                    references: BlogCategoryModel.schema, .id,
                    onDelete: DatabaseSchema.ForeignKeyAction.setNull,
                    onUpdate: .cascade
                )
                .unique(on: BlogPostModel.FieldKeys.v1.slug)
                .create()
        }
        
        func revert(on db: Database) async throws  {
            try await db.schema(BlogCategoryModel.schema).delete()
            try await db.schema(BlogPostModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            
            let categories = (1...4).map { index in
                BlogCategoryModel(title: "Sample category #\(index)")
            }
            try await categories.create(on: db)
            
            try await (1...9).map { index in
                BlogPostModel(
                    id: nil, title: "Sample post #\(index)",
                    slug: "sample-post-\(index)",
                    imageKey: "/img/posts/\(String(format: "%02d", index + 1)).jpg",
                    excerpt: "Lorem ipsum",
                    date: Date().addingTimeInterval(-Double.random(in: 0...(86400 * 60))),
                    content: "Lorem ipsum dolor sit amet.",
                    categoryId: categories[Int.random(in: 0..<categories.count)].id!
                )
            }
            .create(on: db)
        }
        
        func revert(on db: Database) async throws {
            try await BlogPostModel.query(on: db).delete()
            try await BlogCategoryModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift
================================================
import Vapor
import Fluent

final class BlogCategoryModel: DatabaseModelInterface {
    typealias Module = BlogModule

    static let identifier = "categories"
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
        }
    }
    
    @ID() var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Children(for: \.$category)
    var posts: [BlogPostModel]
    
    init() { }
    
    init(
        id: UUID? = nil,
        title: String
    ) {
        self.id = id
        self.title = title
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift
================================================
import Vapor
import Fluent

final class BlogPostModel: DatabaseModelInterface {
    typealias Module = BlogModule
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
            static var slug: FieldKey { "slug" }
            static var imageKey: FieldKey { "image_key" }
            static var excerpt: FieldKey { "excerpt" }
            static var date: FieldKey { "date" }
            static var content: FieldKey { "content" }
            static var categoryId: FieldKey { "category_id" }
        }
    }

    @ID()
    var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Field(key: FieldKeys.v1.slug)
    var slug: String
    
    @Field(key: FieldKeys.v1.imageKey)
    var imageKey: String
    
    @Field(key: FieldKeys.v1.excerpt)
    var excerpt: String
    
    @Field(key: FieldKeys.v1.date)
    var date: Date
    
    @Field(key: FieldKeys.v1.content)
    var content: String
    
    @Parent(key: FieldKeys.v1.categoryId)
    var category: BlogCategoryModel

    init() { }

    init(
        id: UUID? = nil,
        title: String,
        slug: String,
        imageKey: String,
        excerpt: String,
        date: Date,
        content: String,
        categoryId: UUID)
    {
        self.id = id
        self.title = title
        self.slug = slug
        self.imageKey = imageKey
        self.excerpt = excerpt
        self.date = date
        self.content = content
        $category.id = categoryId
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Objects/Blog.swift
================================================
enum Blog {
    
    enum Post {
        
    }

    enum Category {
        
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift
================================================
import Foundation

extension Blog.Category {
    
    struct List: Codable {
        let id: UUID
        let title: String
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift
================================================
import Foundation

extension Blog.Post {
    
    struct List: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
    }
    
    struct Detail: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
        let category: Blog.Category.List
        let content: String
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift
================================================
struct BlogPostContext {
    let post: Blog.Post.Detail
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift
================================================
struct BlogPostsContext {
    let icon: String
    let title: String
    let message: String
    let posts: [Blog.Post.List]
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostTemplate: TemplateRepresentable {

    var context: BlogPostContext
    
    init(
        _ context: BlogPostContext
    ) {
        self.context = context
    }
    
    var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        formatter.timeStyle = .short
        return formatter
    }()

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.post.title)
        ) {
            Div {
                Section {
                    P(dateFormatter.string(from: context.post.date))
                    H1(context.post.title)
                    P(context.post.excerpt)
                }
                .class(["lead", "container"])
                
                Img(src: context.post.image, alt: context.post.title)
                
                Article {
                    Text(context.post.content)
                }
                .class("container")
            }
            .id("post")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostsTemplate: TemplateRepresentable {
    
    var context: BlogPostsContext
    
    init(
        _ context: BlogPostsContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                Div {
                    for post in context.posts {
                        Article {
                            A {
                                Img(src: post.image, alt: post.title)
                                H2(post.title)
                                P(post.excerpt)
                            }
                            .href("/\(post.slug)/")
                        }
                    }
                }
                .class("grid-221")
            }
            .id("blog")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Authenticators/UserCredentialsAuthenticator.swift
================================================
import Vapor
import Fluent

struct UserCredentialsAuthenticator: AsyncCredentialsAuthenticator {
    
    struct Credentials: Content {
        let email: String
        let password: String
    }
    
    func authenticate(
        credentials: Credentials,
        for req: Request
    ) async throws {
        guard
            let user = try await UserAccountModel
                .query(on: req.db)
                .filter(\.$email == credentials.email)
                .first()
        else {
            return
        }
        do {
            guard try Bcrypt.verify(
                credentials.password,
                created: user.password
            ) else {
               return
            }
            req.auth.login(
                AuthenticatedUser(
                    id: user.id!,
                    email: user.email
                )
            )
        }
        catch {
            // do nothing...
        }
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Authenticators/UserSessionAuthenticator.swift
================================================
import Vapor
import Fluent

struct UserSessionAuthenticator: AsyncSessionAuthenticator {
    typealias User = AuthenticatedUser
    
    func authenticate(
        sessionID: User.SessionID,
        for req: Request
    ) async throws {
        guard
            let user = try await UserAccountModel
                .find(sessionID, on: req.db)
        else {
            return
        }
        req.auth.login(
            AuthenticatedUser(
                id: user.id!,
                email: user.email
            )
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Controllers/UserFrontendController.swift
================================================
import Vapor

struct UserFrontendController {
    
    private struct Input: Decodable {
        let email: String?
        let password: String?
    }
    
    private func renderSignInView(
        _ req: Request,
        _ input: Input? = nil,
        _ error: String? = nil
    ) -> Response {
        
        let template = UserLoginTemplate(
            .init(
                icon: "⬇️",
                title: "Sign in",
                message: "Please log in with your existing account",
                email: input?.email,
                password: input?.password,
                error: error
            )
        )
        return req.templates.renderHtml(template)
    }
    
    func signInView(_ req: Request) async throws -> Response {
        renderSignInView(req)
    }
    
    func signInAction(_ req: Request) async throws -> Response {
        /// if the user is authenticated, we can store the user data inside the session too
        if let user = req.auth.get(AuthenticatedUser.self) {
            req.session.authenticate(user)
            return req.redirect(to: "/")
        }
        /// if the user credentials were wrong we render the form again with an error message
        let input = try req.content.decode(Input.self)
        return renderSignInView(
            req,
            input,
            "Invalid email or password."
        )
    }
    
    func signOut(req: Request) throws -> Response {
        req.auth.logout(AuthenticatedUser.self)
        req.session.unauthenticate(AuthenticatedUser.self)
        // req.session.destroy()
        return req.redirect(to: "/")
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Database/Migrations/UserMigrations.swift
================================================
import Vapor
import Fluent

enum UserMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(UserAccountModel.schema)
                .id()
                .field(UserAccountModel.FieldKeys.v1.email, .string, .required)
                .field(UserAccountModel.FieldKeys.v1.password, .string, .required)
                .unique(on: UserAccountModel.FieldKeys.v1.email)
                .create()
        }

        func revert(on db: Database) async throws  {
            try await db.schema(UserAccountModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            let email = "root@localhost.com"
            let password = "ChangeMe1"
            let user = UserAccountModel(
                email: email,
                password: try Bcrypt.hash(password)
            )
            try await user.create(on: db)
        }

        func revert(on db: Database) async throws {
            try await UserAccountModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Database/Models/UserAccountModel.swift
================================================
import Vapor
import Fluent

final class UserAccountModel: DatabaseModelInterface {
    typealias Module = UserModule
    
    struct FieldKeys {
        struct v1 {
            static var email: FieldKey { "email" }
            static var password: FieldKey { "password" }
        }
    }
    
    @ID()
    var id: UUID?
    
    @Field(key: FieldKeys.v1.email)
    var email: String
    
    @Field(key: FieldKeys.v1.password)
    var password: String
    
    init() { }
    
    init(
        id: UUID? = nil,
        email: String,
        password: String
    ) {
        self.id = id
        self.email = email
        self.password = password
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Templates/Contexts/UserLoginContext.swift
================================================
struct UserLoginContext {
    
    let icon: String
    let title: String
    let message: String
    let email: String?
    let password: String?
    let error: String?
    
    init(
        icon: String,
        title: String,
        message: String,
        email: String? = nil,
        password: String? = nil,
        error: String? = nil
    ) {
        self.icon = icon
        self.title = title
        self.message = message
        self.email = email
        self.password = password
        self.error = error
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/Templates/Html/UserLoginTemplate.swift
================================================
import Vapor
import SwiftHtml

struct UserLoginTemplate: TemplateRepresentable {
    
    var context: UserLoginContext
    
    init(
        _ context: UserLoginContext
    ) {
        self.context = context
    }
    
    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")
                
                Form {
                    if let error = context.error {
                        Section {
                            Span(error)
                                .class("error")
                        }
                    }
                    Section {
                        Label("Email:")
                            .for("email")
                                    Input()
                                .key("email")
                                .type(.email)
                                .value(context.email)
                                .class("field")
                    }
                    Section {
                        Label("Password:")
                            .for("password")
                                    Input()
                                .key("password")
                                .type(.password)
                                .value(context.password)
                                .class("field")
                    }
                    Section {
                        Input()
                            .type(.submit)
                            .value("Sign in")
                            .class("submit")
                    }
                }
                .action("/sign-in/")
                .method(.post)
            }
            .id("user-login")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/UserModule.swift
================================================
import Vapor

struct UserModule: ModuleInterface {

    let router = UserRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(UserMigrations.v1())
        app.migrations.add(UserMigrations.seed())
        
        app.middleware.use(UserSessionAuthenticator())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/User/UserRouter.swift
================================================
import Vapor

struct UserRouter: RouteCollection {
    
    let frontendController = UserFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("sign-in", use: frontendController.signInView)
        
        routes
            .grouped(
                UserCredentialsAuthenticator()
            )
            .post("sign-in", use: frontendController.signInAction)
        
        routes.get("sign-out", use: frontendController.signOut)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift
================================================
import Vapor

struct WebFrontendController {
    
    func homeView(req: Request) throws -> Response {
        let ctx = WebHomeContext(
            icon: "👋",
            title: "Home",
            message: "Hi there, welcome to my page.",
            paragraphs: [
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
                "Nisi ut aliquip ex ea commodo consequat.",
            ],
            link: .init(
                label: "Read my blog →",
                url: "/blog/"
            )
        )
        
        return req.templates.renderHtml(
            WebHomeTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift
================================================
struct WebHomeContext {
    let icon: String
    let title: String
    let message: String
    let paragraphs: [String]
    let link: WebLinkContext
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift
================================================
public struct WebIndexContext {
    
    public let title: String
    
    public init(
        title: String
    ) {
        self.title = title
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift
================================================
public struct WebLinkContext {
    
    public let label: String
    public let url: String
    
    public init(
        label: String,
        url: String
    ) {
        self.label = label
        self.url = url
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebHomeTemplate: TemplateRepresentable {

    var context: WebHomeContext
    
    init(
        _ context: WebHomeContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                for paragraph in context.paragraphs {
                    P(paragraph)
                }

                WebLinkTemplate(context.link).render(req)
            }
            .id("home")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift
================================================
import Vapor
import SwiftHtml
import SwiftSvg

extension Svg {
    
    static func menuIcon() -> Svg {
        Svg {
            Line(x1: 3, y1: 12, x2: 21, y2: 12)
            Line(x1: 3, y1: 6, x2: 21, y2: 6)
            Line(x1: 3, y1: 18, x2: 21, y2: 18)
        }
        .width(24)
        .height(24)
        .viewBox(minX: 0, minY: 0, width: 24, height: 24)
        .fill("none")
        .stroke("currentColor")
        .strokeWidth(2)
        .strokeLinecap("round")
        .strokeLinejoin("round")
    }
}

public struct WebIndexTemplate: TemplateRepresentable {

    public var context: WebIndexContext
    var body: Tag

    public init(
        _ context: WebIndexContext,
        @TagBuilder _ builder: () -> Tag
    ) {
        self.context = context
        self.body = builder()
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Html {
            Head {
                Meta()
                    .charset("utf-8")
                Meta()
                    .name(.viewport)
                    .content("width=device-width, initial-scale=1")

                Link(rel: .shortcutIcon)
                    .href("/img/favicon.ico")
                    .type("image/x-icon")
                Link(rel: .stylesheet)
                    .href("https://cdn.jsdelivr.net/gh/feathercms/feather-core@1.0.0-beta.44/feather.min.css")
                Link(rel: .stylesheet)
                    .href("/css/web.css")
                
                Title(context.title)
            }
            Body {
                Header {
                    Div {
                        A {
                            Img(src: "/img/logo.png", alt: "Logo")
                        }
                        .id("site-logo")
                        .href("/")
                        
                        Nav {
                            Input()
                                .type(.checkbox)
                                .id("primary-menu-button")
                                .name("menu-button")
                                .class("menu-button")
                            Label {
                                Svg.menuIcon()
                            }
                            .for("primary-menu-button")
                            Div {
                                A("Home")
                                    .href("/")
                                    .class("selected", req.url.path == "/")
                                A("Blog")
                                    .href("/blog/")
                                    .class("selected", req.url.path == "/blog/")
                                A("About")
                                    .href("#")
                                    .onClick("javascript:about();")
                                
                                if req.auth.has(AuthenticatedUser.self) {
                                    A("Sign out")
                                        .href("/sign-out/")
                                }
                                else {
                                    A("Sign in")
                                        .href("/sign-in/")
                                }
                            }
                            .class("menu-items")
                        }
                        .id("primary-menu")
                    }
                    .id("navigation")
                }
                
                Main {
                    body
                }

                Footer {
                    Section {
                        P {
                            Text("This site is powered by ")
                            A("Swift")
                                .href("https://swift.org")
                                .target(.blank)
                            Text(" & ")
                            A("Vapor")
                                .href("https://vapor.codes")
                                .target(.blank)
                            Text(".")
                        }
                        P("myPage &copy; 2020-2022")
                    }
                }
                
                Script()
                    .type(.javascript)
                    .src("/js/web.js")
                
            }
        }
        .lang("en-US")
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebLinkTemplate: TemplateRepresentable {

    var context: WebLinkContext
    
    init(
        _ context: WebLinkContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        A(context.label)
            .href(context.url)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/WebModule.swift
================================================
import Vapor

struct WebModule: ModuleInterface {

    let router = WebRouter()

    func boot(_ app: Application) throws {
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Modules/Web/WebRouter.swift
================================================
import Vapor

struct WebRouter: RouteCollection {
    
    let frontendController = WebFrontendController()

    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get(use: frontendController.homeView)
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Template/Request+Template.swift
================================================
import Vapor

public extension Request {
    var templates: TemplateRenderer { .init(self) }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Template/TemplateRenderer.swift
================================================
import Vapor
import SwiftHtml

public struct TemplateRenderer {
    
    var req: Request
    
    init(_ req: Request) {
        self.req = req
    }

    public func renderHtml(
        _ template: TemplateRepresentable,
        minify: Bool = false,
        indent: Int = 4
    ) -> Response {
        let doc = Document(.html) {
            template.render(req)
        }
        let body = DocumentRenderer(
            minify: minify,
            indent: indent
        )
        .render(doc)
        return Response(
            status: .ok,
            headers: [
                "content-type": "text/html"
            ],
            body: .init(string: body)
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/App/Template/TemplateRepresentable.swift
================================================
import Vapor
import SwiftSgml

public protocol TemplateRepresentable {
    
    @TagBuilder
    func render(_ req: Request) -> Tag
}


================================================
FILE: Chapter 05/myProject/Sources/App/configure.swift
================================================
import Vapor
import Fluent
import FluentSQLiteDriver

public func configure(
    _ app: Application
) throws {

    let dbPath = app.directory.resourcesDirectory + "db.sqlite"
    app.databases.use(.sqlite(.file(dbPath)), as: .sqlite)
    
    app.middleware.use(
        FileMiddleware(
            publicDirectory: app.directory.publicDirectory
        )
    )

    app.middleware.use(ExtendPathMiddleware())

    app.sessions.use(.fluent)
    app.migrations.add(SessionRecord.migration)
    app.middleware.use(app.sessions.middleware)

    let modules: [ModuleInterface] = [
        WebModule(),
        UserModule(),
        BlogModule(),
    ]
    for module in modules {
        try module.boot(app)
    }
    
    try app.autoMigrate().wait()
}


================================================
FILE: Chapter 05/myProject/Sources/App/routes.swift
================================================
import Vapor
import SwiftHtml

func routes(_ app: Application) throws {

    app.routes.get { req -> Response in
        req.templates.renderHtml(
            WebIndexTemplate(
                WebIndexContext(
                    title: "Home"
                )
            ) {
                P("Hi there, welcome to my page!")
            }
        )
    }
}


================================================
FILE: Chapter 05/myProject/Sources/Run/main.swift
================================================
import App
import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try app.run()


================================================
FILE: Chapter 05/myProject/Tests/AppTests/AppTests.swift
================================================
@testable import App
import XCTVapor

final class AppTests: XCTestCase {
    func testHelloWorld() throws {
        let app = Application(.testing)
        defer { app.shutdown() }
        try configure(app)

        try app.test(.GET, "hello", afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            XCTAssertEqual(res.body.string, "Hello, world!")
        })
    }
}


================================================
FILE: Chapter 05/myProject/docker-compose.yml
================================================
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker compose build
#      Start app: docker compose up app
#       Stop all: docker compose down
#

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  
services:
  app:
    image: myproject:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 06/myProject/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 06/myProject/Package.swift
================================================
// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v12)
    ],
    dependencies: [
        .package(
            url: "https://github.com/vapor/vapor",
            from: "4.70.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent",
            from: "4.4.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent-sqlite-driver",
            from: "4.1.0"
        ),
        .package(
            url: "https://github.com/binarybirds/swift-html",
            from: "1.7.0"
        ),
    ],
    targets: [
        .target(name: "App", dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            .product(name: "SwiftHtml", package: "swift-html"),
            .product(name: "SwiftSvg", package: "swift-html"),
        ]),
        .executableTarget(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)


================================================
FILE: Chapter 06/myProject/Public/css/web.css
================================================
#blog h2 {
    margin: 0.5rem 0;
}


================================================
FILE: Chapter 06/myProject/Public/js/web.js
================================================
function about() {
    alert("myPage\n\nversion 1.0.0");
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/AuthenticatedUser.swift
================================================
import Vapor

public struct AuthenticatedUser {

    public let id: UUID
    public let email: String
    
    public init(
        id: UUID,
        email: String
    ) {
        self.id = id
        self.email = email
    }
}

extension AuthenticatedUser: SessionAuthenticatable {
    public var sessionID: UUID { id }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/DatabaseModelInterface.swift
================================================
import Vapor
import Fluent

public protocol DatabaseModelInterface: Fluent.Model
    where Self.IDValue == UUID
{    
    associatedtype Module: ModuleInterface

    static var identifier: String { get }
}

public extension DatabaseModelInterface {

    static var schema: String {
        Module.identifier + "_" + identifier
    }
    
    static var identifier: String {
        String(describing: self)
            .dropFirst(Module.identifier.count)
            .dropLast(5)
            .lowercased() + "s"
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/AbstractForm.swift
================================================
import Vapor

open class AbstractForm: FormComponent {
    
    open var action: FormAction
    open var fields: [FormComponent]
    open var error: String?
    open var submit: String?
    
    public init(
        action: FormAction = .init(),
        fields: [FormComponent] = [],
        error: String? = nil,
        submit: String? = nil
    ) {
        self.action = action
        self.fields = fields
        self.error = error
        self.submit = submit
        
        self.action.enctype = .multipart
    }
        
    open func load(req: Request) async throws {
        for field in fields {
            try await field.load(req: req)
        }
    }
    
    open func process(req: Request) async throws {
        for field in fields {
            try await field.process(req: req)
        }
    }
    
    open func validate(req: Request) async throws -> Bool {
        var result: [Bool] = []
        for field in fields {
            result.append(try await field.validate(req: req))
        }
        return result.filter { $0 == false }.isEmpty
    }
    
    open func write(req: Request) async throws {
        for field in fields {
            try await field.write(req: req)
        }
    }
    
    open func save(req: Request) async throws {
        for field in fields {
            try await field.save(req: req)
        }
    }
    
    open func read(req: Request) async throws {
        for field in fields {
            try await field.read(req: req)
        }
    }
    
    open func render(req: Request) -> TemplateRepresentable {
        FormTemplate(
            .init(
                action: action,
                fields: fields.map { $0.render(req: req)},
                error: error,
                submit: submit
            )
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/AbstractFormField.swift
================================================
import Vapor

open class AbstractFormField<
    Input: Decodable,
    Output: TemplateRepresentable
>: FormComponent {
    
    public var key: String
    public var input: Input
    public var output: Output
    public var error: String?

    public init(
        key: String,
        input: Input,
        output: Output,
        error: String? = nil
    ) {
        self.key = key
        self.input = input
        self.output = output
        self.error = error
    }

    open func config(
        _ block: (AbstractFormField<Input, Output>) -> Void
    ) -> Self {
        block(self)
        return self
    }
    
    open func load(req: Vapor.Request) async throws {
        
    }
    
    open func process(req: Vapor.Request) async throws {
        if let value = try? req.content.get(Input.self, at: key) {
            input = value
        }
    }
    
    open func validate(req: Vapor.Request) async throws -> Bool {
        true
    }
    
    open func write(req: Vapor.Request) async throws {
        
    }
    
    open func save(req: Vapor.Request) async throws {
        
    }
    
    open func read(req: Vapor.Request) async throws {
        
    }
    
    open func render(req: Vapor.Request) -> TemplateRepresentable {
        output
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Fields/InputField.swift
================================================
public final class InputField: AbstractFormField<
    String,
    InputFieldTemplate
> {

    public convenience init(_ key: String) {
        self.init(
            key: key,
            input: "",
            output: .init(
                .init(
                    key: key
                )
            )
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/FormAction.swift
================================================
import SwiftHtml

public struct FormAction {
    
    public var method: SwiftHtml.Method
    public var url: String?
    public var enctype: SwiftHtml.Enctype?
    
    public init(
        method: SwiftHtml.Method = .post,
        url: String? = nil,
        enctype: SwiftHtml.Enctype? = nil
    ) {
        self.method = method
        self.url = url
        self.enctype = enctype
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/FormComponent.swift
================================================
import Vapor

public protocol FormComponent {
    
    func load(req: Request) async throws
    func process(req: Request) async throws
    func validate(req: Request) async throws -> Bool
    func write(req: Request) async throws
    func save(req: Request) async throws
    func read(req: Request) async throws
    func render(req: Request) -> TemplateRepresentable
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/FormComponentBuilder.swift
================================================
@resultBuilder
public enum FormComponentBuilder {
    
    public static func buildBlock(
        _ components: FormComponent...
    ) -> [FormComponent] {
        components
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/FormContext.swift
================================================
public struct FormContext {
    public var action: FormAction
    public var fields: [TemplateRepresentable]
    public var error: String?
    public var submit: String?
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/InputFieldContext.swift
================================================
import SwiftHtml

public struct InputFieldContext {
    
    public let key: String
    public var label: LabelContext
    public var type: SwiftHtml.Input.`Type`
    public var placeholder: String?
    public var value: String?
    public var error: String?
    
    public init(
        key: String,
        label: LabelContext? = nil,
        type: Input.`Type` = .text,
        placeholder: String? = nil,
        value: String? = nil,
        error: String? = nil
    ) {
        self.key = key
        self.label = label ?? .init(key: key)
        self.type = type
        self.placeholder = placeholder
        self.value = value
        self.error = error
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/LabelContext.swift
================================================
public struct LabelContext {
    
    public var key: String
    public var title: String?
    public var required: Bool
    public var more: String?
    
    public init(
        key: String,
        title: String? = nil,
        required: Bool = false,
        more: String? = nil
    ) {
        self.key = key
        self.title = title
        self.required = required
        self.more = more
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/FormTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct FormTemplate: TemplateRepresentable {
    
    var context: FormContext
    
    public init(
        _ context: FormContext
    ) {
        self.context = context
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Form {
            if let error = context.error {
                Section {
                    P(error)
                        .class("error")
                }
            }
            for field in context.fields {
                Section {
                    field.render(req)
                }
            }
            
            Section {
                Input()
                    .type(.submit)
                    .value(context.submit ?? "Save")
            }
        }
        .method(context.action.method)
        .action(context.action.url)
        .enctype(context.action.enctype)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/InputFieldTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct InputFieldTemplate: TemplateRepresentable {

    public var context: InputFieldContext
    
    public init(
        _ context: InputFieldContext
    ) {
        self.context = context
    }
    
    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        LabelTemplate(context.label).render(req)

        Input()
            .type(context.type)
            .key(context.key)
            .placeholder(context.placeholder)
            .value(context.value)
            .class("field")
        if let error = context.error {
            Span(error)
                .class("error")
        }
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/LabelTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct LabelTemplate: TemplateRepresentable {

    var context: LabelContext

    public init(
        _ context: LabelContext
    ) {
        self.context = context
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Label {
            Text(context.title ?? context.key.capitalized)

            if let more = context.more {
                Span(more)
                    .class("more")
            }
            if context.required {
                Span("*")
                    .class("required")
            }
        }.for(context.key)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Framework/ModuleInterface.swift
================================================
import Vapor

public protocol ModuleInterface {
    
    static var identifier: String { get }

    func boot(_ app: Application) throws
}

public extension ModuleInterface {

    func boot(_ app: Application) throws {}

    static var identifier: String {
        String(describing: self).dropLast(6).lowercased()
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift
================================================
import Vapor

struct ExtendPathMiddleware: AsyncMiddleware {

    func respond(
        to req: Request,
        chainingTo next: AsyncResponder
    ) async throws -> Response {
        if !req.url.path.hasSuffix("/") && !req.url.path.contains(".") {
            return req.redirect(
                to: req.url.path + "/",
                redirectType: .permanent
            )
        }
        return try await next.respond(
            to: req
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/BlogModule.swift
================================================
import Vapor

struct BlogModule: ModuleInterface {

    let router = BlogRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(BlogMigrations.v1())
        app.migrations.add(BlogMigrations.seed())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/BlogRouter.swift
================================================
import Vapor

struct BlogRouter: RouteCollection {
    
    let controller = BlogFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("blog", use: controller.blogView)
        routes.get(.anything, use: controller.postView)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift
================================================
import Vapor
import Fluent

struct BlogFrontendController {
    
    func blogView(req: Request) async throws -> Response {
        let postModels = try await BlogPostModel
            .query(on: req.db)
            .sort(\.$date, .descending)
            .all()

        let posts = try postModels.map {
            Blog.Post.List(
                id: try $0.requireID(),
                title: $0.title,
                slug: $0.slug,
                image: $0.imageKey,
                excerpt: $0.excerpt,
                date: $0.date
            )
        }

        let ctx = BlogPostsContext(
            icon: "🔥",
            title: "Blog",
            message: "Hot news and stories about everything.",
            posts: posts)

        return req.templates.renderHtml(
            BlogPostsTemplate(ctx)
        )
    }

    func postView(req: Request) async throws -> Response {
        let slug = req.url.path.trimmingCharacters(
            in: .init(charactersIn: "/")
        )
        guard
            let post = try await BlogPostModel
                .query(on: req.db)
                .filter(\.$slug == slug)
                .with(\.$category)
                .first()
        else {
            return req.redirect(to: "/")
        }
        let ctx = BlogPostContext(
            post: Blog.Post.Detail(
                id: post.id!,
                title: post.title,
                slug: post.slug,
                image: post.imageKey,
                excerpt: post.excerpt,
                date: post.date,
                category: .init(
                    id: post.category.id!,
                    title: post.category.title
                ),
                content: post.content
            )
        )

        return req.templates.renderHtml(
            BlogPostTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift
================================================
import Foundation
import Fluent

enum BlogMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(BlogCategoryModel.schema)
                .id()
                .field(BlogCategoryModel.FieldKeys.v1.title, .string, .required)
                .create()
            
            try await db.schema(BlogPostModel.schema)
                .id()
                .field(BlogPostModel.FieldKeys.v1.title, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.slug, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.imageKey, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.excerpt, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.date, .datetime, .required)
                .field(BlogPostModel.FieldKeys.v1.content, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.categoryId, .uuid)
                .foreignKey(
                    BlogPostModel.FieldKeys.v1.categoryId,
                    references: BlogCategoryModel.schema, .id,
                    onDelete: DatabaseSchema.ForeignKeyAction.setNull,
                    onUpdate: .cascade
                )
                .unique(on: BlogPostModel.FieldKeys.v1.slug)
                .create()
        }
        
        func revert(on db: Database) async throws  {
            try await db.schema(BlogCategoryModel.schema).delete()
            try await db.schema(BlogPostModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            
            let categories = (1...4).map { index in
                BlogCategoryModel(title: "Sample category #\(index)")
            }
            try await categories.create(on: db)
            
            try await (1...9).map { index in
                BlogPostModel(
                    id: nil, title: "Sample post #\(index)",
                    slug: "sample-post-\(index)",
                    imageKey: "/img/posts/\(String(format: "%02d", index + 1)).jpg",
                    excerpt: "Lorem ipsum",
                    date: Date().addingTimeInterval(-Double.random(in: 0...(86400 * 60))),
                    content: "Lorem ipsum dolor sit amet.",
                    categoryId: categories[Int.random(in: 0..<categories.count)].id!
                )
            }
            .create(on: db)
        }
        
        func revert(on db: Database) async throws {
            try await BlogPostModel.query(on: db).delete()
            try await BlogCategoryModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift
================================================
import Vapor
import Fluent

final class BlogCategoryModel: DatabaseModelInterface {
    typealias Module = BlogModule

    static let identifier = "categories"
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
        }
    }
    
    @ID() var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Children(for: \.$category)
    var posts: [BlogPostModel]
    
    init() { }
    
    init(
        id: UUID? = nil,
        title: String
    ) {
        self.id = id
        self.title = title
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift
================================================
import Vapor
import Fluent

final class BlogPostModel: DatabaseModelInterface {
    typealias Module = BlogModule
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
            static var slug: FieldKey { "slug" }
            static var imageKey: FieldKey { "image_key" }
            static var excerpt: FieldKey { "excerpt" }
            static var date: FieldKey { "date" }
            static var content: FieldKey { "content" }
            static var categoryId: FieldKey { "category_id" }
        }
    }

    @ID()
    var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Field(key: FieldKeys.v1.slug)
    var slug: String
    
    @Field(key: FieldKeys.v1.imageKey)
    var imageKey: String
    
    @Field(key: FieldKeys.v1.excerpt)
    var excerpt: String
    
    @Field(key: FieldKeys.v1.date)
    var date: Date
    
    @Field(key: FieldKeys.v1.content)
    var content: String
    
    @Parent(key: FieldKeys.v1.categoryId)
    var category: BlogCategoryModel

    init() { }

    init(
        id: UUID? = nil,
        title: String,
        slug: String,
        imageKey: String,
        excerpt: String,
        date: Date,
        content: String,
        categoryId: UUID)
    {
        self.id = id
        self.title = title
        self.slug = slug
        self.imageKey = imageKey
        self.excerpt = excerpt
        self.date = date
        self.content = content
        $category.id = categoryId
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Objects/Blog.swift
================================================
enum Blog {
    
    enum Post {
        
    }

    enum Category {
        
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift
================================================
import Foundation

extension Blog.Category {
    
    struct List: Codable {
        let id: UUID
        let title: String
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift
================================================
import Foundation

extension Blog.Post {
    
    struct List: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
    }
    
    struct Detail: Codable {
        let id: UUID
        let title: String
        let slug: String
        let image: String
        let excerpt: String
        let date: Date
        let category: Blog.Category.List
        let content: String
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift
================================================
struct BlogPostContext {
    let post: Blog.Post.Detail
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift
================================================
struct BlogPostsContext {
    let icon: String
    let title: String
    let message: String
    let posts: [Blog.Post.List]
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostTemplate: TemplateRepresentable {

    var context: BlogPostContext
    
    init(
        _ context: BlogPostContext
    ) {
        self.context = context
    }
    
    var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        formatter.timeStyle = .short
        return formatter
    }()

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.post.title)
        ) {
            Div {
                Section {
                    P(dateFormatter.string(from: context.post.date))
                    H1(context.post.title)
                    P(context.post.excerpt)
                }
                .class(["lead", "container"])
                
                Img(src: context.post.image, alt: context.post.title)
                
                Article {
                    Text(context.post.content)
                }
                .class("container")
            }
            .id("post")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift
================================================
import Vapor
import SwiftHtml

struct BlogPostsTemplate: TemplateRepresentable {
    
    var context: BlogPostsContext
    
    init(
        _ context: BlogPostsContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                Div {
                    for post in context.posts {
                        Article {
                            A {
                                Img(src: post.image, alt: post.title)
                                H2(post.title)
                                P(post.excerpt)
                            }
                            .href("/\(post.slug)/")
                        }
                    }
                }
                .class("grid-221")
            }
            .id("blog")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Authenticators/UserCredentialsAuthenticator.swift
================================================
import Vapor
import Fluent

struct UserCredentialsAuthenticator: AsyncCredentialsAuthenticator {
    
    struct Credentials: Content {
        let email: String
        let password: String
    }
    
    func authenticate(
        credentials: Credentials,
        for req: Request
    ) async throws {
        guard
            let user = try await UserAccountModel
                .query(on: req.db)
                .filter(\.$email == credentials.email)
                .first()
        else {
            return
        }
        do {
            guard try Bcrypt.verify(
                credentials.password,
                created: user.password
            ) else {
               return
            }
            req.auth.login(
                AuthenticatedUser(
                    id: user.id!,
                    email: user.email
                )
            )
        }
        catch {
            // do nothing...
        }
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Authenticators/UserSessionAuthenticator.swift
================================================
import Vapor
import Fluent

struct UserSessionAuthenticator: AsyncSessionAuthenticator {
    typealias User = AuthenticatedUser
    
    func authenticate(
        sessionID: User.SessionID,
        for req: Request
    ) async throws {
        guard
            let user = try await UserAccountModel
                .find(sessionID, on: req.db)
        else {
            return
        }
        req.auth.login(
            AuthenticatedUser(
                id: user.id!,
                email: user.email
            )
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Controllers/UserFrontendController.swift
================================================
import Vapor

struct UserFrontendController {
    
    private func renderSignInView(
        _ req: Request,
        _ form: UserLoginForm
    ) -> Response {
        let template = UserLoginTemplate(
            .init(
                icon: "⬇️",
                title: "Sign in",
                message: "Please log in with your existing account",
                form: form.render(req: req)
            )
        )
        return req.templates.renderHtml(template)
    }
    
    func signInView(
        _ req: Request
    ) async throws -> Response {
        renderSignInView(req, .init())
    }

    func signInAction(
        _ req: Request
    ) async throws -> Response {
        /// the user is authenticated, we can store the user data inside the session too
        if let user = req.auth.get(AuthenticatedUser.self) {
            req.session.authenticate(user)
            return req.redirect(to: "/")
        }
        let form = UserLoginForm()
        try await form.process(req: req)
        form.error = "Invalid email or password."
        return renderSignInView(req, form)
    }
    
    func signOut(
        _ req: Request
    ) throws -> Response {
        req.auth.logout(AuthenticatedUser.self)
        req.session.unauthenticate(AuthenticatedUser.self)
        return req.redirect(to: "/")
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Database/Migrations/UserMigrations.swift
================================================
import Vapor
import Fluent

enum UserMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(UserAccountModel.schema)
                .id()
                .field(UserAccountModel.FieldKeys.v1.email, .string, .required)
                .field(UserAccountModel.FieldKeys.v1.password, .string, .required)
                .unique(on: UserAccountModel.FieldKeys.v1.email)
                .create()
        }

        func revert(on db: Database) async throws  {
            try await db.schema(UserAccountModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            let email = "root@localhost.com"
            let password = "ChangeMe1"
            let user = UserAccountModel(
                email: email,
                password: try Bcrypt.hash(password)
            )
            try await user.create(on: db)
        }

        func revert(on db: Database) async throws {
            try await UserAccountModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Database/Models/UserAccountModel.swift
================================================
import Vapor
import Fluent

final class UserAccountModel: DatabaseModelInterface {
    typealias Module = UserModule
    
    struct FieldKeys {
        struct v1 {
            static var email: FieldKey { "email" }
            static var password: FieldKey { "password" }
        }
    }
    
    @ID()
    var id: UUID?
    
    @Field(key: FieldKeys.v1.email)
    var email: String
    
    @Field(key: FieldKeys.v1.password)
    var password: String
    
    init() { }
    
    init(
        id: UUID? = nil,
        email: String,
        password: String
    ) {
        self.id = id
        self.email = email
        self.password = password
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Forms/UserLoginForm.swift
================================================
import Vapor

final class UserLoginForm: AbstractForm {
    
    public convenience init() {
        self.init(
            action: .init(
                method: .post,
                url: "/sign-in/"
            ),
            submit: "Sign in"
        )
        self.fields = createFields()
    }

    @FormComponentBuilder
    func createFields() -> [FormComponent] {
        InputField("email")
            .config {
                $0.output.context.label.required = true
                $0.output.context.type = .email
            }
        InputField("password")
            .config {
                $0.output.context.label.required = true
                $0.output.context.type = .password
            }
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Templates/Contexts/UserLoginContext.swift
================================================
struct UserLoginContext {
    
    let icon: String
    let title: String
    let message: String
    let form: TemplateRepresentable
    
    init(
        icon: String,
        title: String,
        message: String,
        form: TemplateRepresentable
    ) {
        self.icon = icon
        self.title = title
        self.message = message
        self.form = form
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/Templates/Html/UserLoginTemplate.swift
================================================
import Vapor
import SwiftHtml

struct UserLoginTemplate: TemplateRepresentable {

    var context: UserLoginContext
    
    init(
        _ context: UserLoginContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                context.form.render(req)
            }
            .id("user-login")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/UserModule.swift
================================================
import Vapor

struct UserModule: ModuleInterface {

    let router = UserRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(UserMigrations.v1())
        app.migrations.add(UserMigrations.seed())
        
        app.middleware.use(UserSessionAuthenticator())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/User/UserRouter.swift
================================================
import Vapor

struct UserRouter: RouteCollection {
    
    let frontendController = UserFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("sign-in", use: frontendController.signInView)
        
        routes
            .grouped(
                UserCredentialsAuthenticator()
            )
            .post("sign-in", use: frontendController.signInAction)
        
        routes.get("sign-out", use: frontendController.signOut)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift
================================================
import Vapor

struct WebFrontendController {
    
    func homeView(req: Request) throws -> Response {
        let ctx = WebHomeContext(
            icon: "👋",
            title: "Home",
            message: "Hi there, welcome to my page.",
            paragraphs: [
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
                "Nisi ut aliquip ex ea commodo consequat.",
            ],
            link: .init(
                label: "Read my blog →",
                url: "/blog/"
            )
        )
        
        return req.templates.renderHtml(
            WebHomeTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift
================================================
struct WebHomeContext {
    let icon: String
    let title: String
    let message: String
    let paragraphs: [String]
    let link: WebLinkContext
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift
================================================
public struct WebIndexContext {
    
    public let title: String
    
    public init(
        title: String
    ) {
        self.title = title
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift
================================================
public struct WebLinkContext {
    
    public let label: String
    public let url: String
    
    public init(
        label: String,
        url: String
    ) {
        self.label = label
        self.url = url
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebHomeTemplate: TemplateRepresentable {

    var context: WebHomeContext
    
    init(
        _ context: WebHomeContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        WebIndexTemplate(
            .init(title: context.title)
        ) {
            Div {
                Section {
                    P(context.icon)
                    H1(context.title)
                    P(context.message)
                }
                .class("lead")

                for paragraph in context.paragraphs {
                    P(paragraph)
                }

                WebLinkTemplate(context.link).render(req)
            }
            .id("home")
            .class("container")
        }
        .render(req)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift
================================================
import Vapor
import SwiftHtml
import SwiftSvg

extension Svg {
    
    static func menuIcon() -> Svg {
        Svg {
            Line(x1: 3, y1: 12, x2: 21, y2: 12)
            Line(x1: 3, y1: 6, x2: 21, y2: 6)
            Line(x1: 3, y1: 18, x2: 21, y2: 18)
        }
        .width(24)
        .height(24)
        .viewBox(minX: 0, minY: 0, width: 24, height: 24)
        .fill("none")
        .stroke("currentColor")
        .strokeWidth(2)
        .strokeLinecap("round")
        .strokeLinejoin("round")
    }
}

public struct WebIndexTemplate: TemplateRepresentable {

    public var context: WebIndexContext
    var body: Tag

    public init(
        _ context: WebIndexContext,
        @TagBuilder _ builder: () -> Tag
    ) {
        self.context = context
        self.body = builder()
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Html {
            Head {
                Meta()
                    .charset("utf-8")
                Meta()
                    .name(.viewport)
                    .content("width=device-width, initial-scale=1")

                Link(rel: .shortcutIcon)
                    .href("/img/favicon.ico")
                    .type("image/x-icon")
                Link(rel: .stylesheet)
                    .href("https://cdn.jsdelivr.net/gh/feathercms/feather-core@1.0.0-beta.44/feather.min.css")
                Link(rel: .stylesheet)
                    .href("/css/web.css")
                
                Title(context.title)
            }
            Body {
                Header {
                    Div {
                        A {
                            Img(src: "/img/logo.png", alt: "Logo")
                        }
                        .id("site-logo")
                        .href("/")
                        
                        Nav {
                            Input()
                                .type(.checkbox)
                                .id("primary-menu-button")
                                .name("menu-button")
                                .class("menu-button")
                            Label {
                                Svg.menuIcon()
                            }
                            .for("primary-menu-button")
                            Div {
                                A("Home")
                                    .href("/")
                                    .class("selected", req.url.path == "/")
                                A("Blog")
                                    .href("/blog/")
                                    .class("selected", req.url.path == "/blog/")
                                A("About")
                                    .href("#")
                                    .onClick("javascript:about();")
                                
                                if req.auth.has(AuthenticatedUser.self) {
                                    A("Sign out")
                                        .href("/sign-out/")
                                }
                                else {
                                    A("Sign in")
                                        .href("/sign-in/")
                                }
                            }
                            .class("menu-items")
                        }
                        .id("primary-menu")
                    }
                    .id("navigation")
                }
                
                Main {
                    body
                }

                Footer {
                    Section {
                        P {
                            Text("This site is powered by ")
                            A("Swift")
                                .href("https://swift.org")
                                .target(.blank)
                            Text(" & ")
                            A("Vapor")
                                .href("https://vapor.codes")
                                .target(.blank)
                            Text(".")
                        }
                        P("myPage &copy; 2020-2022")
                    }
                }
                
                Script()
                    .type(.javascript)
                    .src("/js/web.js")
                
            }
        }
        .lang("en-US")
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift
================================================
import Vapor
import SwiftHtml

struct WebLinkTemplate: TemplateRepresentable {

    var context: WebLinkContext
    
    init(
        _ context: WebLinkContext
    ) {
        self.context = context
    }

    @TagBuilder
    func render(
        _ req: Request
    ) -> Tag {
        A(context.label)
            .href(context.url)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/WebModule.swift
================================================
import Vapor

struct WebModule: ModuleInterface {

    let router = WebRouter()

    func boot(_ app: Application) throws {
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Modules/Web/WebRouter.swift
================================================
import Vapor

struct WebRouter: RouteCollection {
    
    let frontendController = WebFrontendController()

    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get(use: frontendController.homeView)
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Template/Request+Template.swift
================================================
import Vapor

public extension Request {
    var templates: TemplateRenderer { .init(self) }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Template/TemplateRenderer.swift
================================================
import Vapor
import SwiftHtml

public struct TemplateRenderer {
    
    var req: Request
    
    init(_ req: Request) {
        self.req = req
    }

    public func renderHtml(
        _ template: TemplateRepresentable,
        minify: Bool = false,
        indent: Int = 4
    ) -> Response {
        let doc = Document(.html) {
            template.render(req)
        }
        let body = DocumentRenderer(
            minify: minify,
            indent: indent
        )
        .render(doc)
        return Response(
            status: .ok,
            headers: [
                "content-type": "text/html"
            ],
            body: .init(string: body)
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/App/Template/TemplateRepresentable.swift
================================================
import Vapor
import SwiftSgml

public protocol TemplateRepresentable {
    
    @TagBuilder
    func render(_ req: Request) -> Tag
}


================================================
FILE: Chapter 06/myProject/Sources/App/configure.swift
================================================
import Vapor
import Fluent
import FluentSQLiteDriver

public func configure(
    _ app: Application
) throws {

    let dbPath = app.directory.resourcesDirectory + "db.sqlite"
    app.databases.use(.sqlite(.file(dbPath)), as: .sqlite)
    
    app.middleware.use(
        FileMiddleware(
            publicDirectory: app.directory.publicDirectory
        )
    )

    app.middleware.use(ExtendPathMiddleware())

    app.sessions.use(.fluent)
    app.migrations.add(SessionRecord.migration)
    app.middleware.use(app.sessions.middleware)

    let modules: [ModuleInterface] = [
        WebModule(),
        UserModule(),
        BlogModule(),
    ]
    for module in modules {
        try module.boot(app)
    }
    
    try app.autoMigrate().wait()
}


================================================
FILE: Chapter 06/myProject/Sources/App/routes.swift
================================================
import Vapor
import SwiftHtml

func routes(_ app: Application) throws {

    app.routes.get { req -> Response in
        req.templates.renderHtml(
            WebIndexTemplate(
                WebIndexContext(
                    title: "Home"
                )
            ) {
                P("Hi there, welcome to my page!")
            }
        )
    }
}


================================================
FILE: Chapter 06/myProject/Sources/Run/main.swift
================================================
import App
import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try app.run()


================================================
FILE: Chapter 06/myProject/Tests/AppTests/AppTests.swift
================================================
@testable import App
import XCTVapor

final class AppTests: XCTestCase {
    func testHelloWorld() throws {
        let app = Application(.testing)
        defer { app.shutdown() }
        try configure(app)

        try app.test(.GET, "hello", afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            XCTAssertEqual(res.body.string, "Hello, world!")
        })
    }
}


================================================
FILE: Chapter 06/myProject/docker-compose.yml
================================================
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker compose build
#      Start app: docker compose up app
#       Stop all: docker compose down
#

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  
services:
  app:
    image: myproject:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 07/myProject/Dockerfile
================================================
# ================================
# Build image
# ================================
FROM swift:6.0-noble AS build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get install -y libjemalloc-dev

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build the application, with optimizations, with static linking, and using jemalloc
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \
        --product myProject \
        --static-swift-stdlib \
        -Xlinker -ljemalloc

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/myProject" ./

# Copy static swift backtracer binary to staging area
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:noble

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      libjemalloc2 \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./myProject"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]


================================================
FILE: Chapter 07/myProject/Package.swift
================================================
// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "myProject",
    platforms: [
       .macOS(.v12)
    ],
    dependencies: [
        .package(
            url: "https://github.com/vapor/vapor",
            from: "4.70.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent",
            from: "4.4.0"
        ),
        .package(
            url: "https://github.com/vapor/fluent-sqlite-driver",
            from: "4.1.0"
        ),
        .package(
            url: "https://github.com/binarybirds/swift-html",
            from: "1.7.0"
        ),
    ],
    targets: [
        .target(name: "App", dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            .product(name: "SwiftHtml", package: "swift-html"),
            .product(name: "SwiftSvg", package: "swift-html"),
        ]),
        .executableTarget(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)


================================================
FILE: Chapter 07/myProject/Public/css/web.css
================================================
#blog h2 {
    margin: 0.5rem 0;
}


================================================
FILE: Chapter 07/myProject/Public/js/web.js
================================================
function about() {
    alert("myPage\n\nversion 1.0.0");
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/AuthenticatedUser.swift
================================================
import Vapor

public struct AuthenticatedUser {

    public let id: UUID
    public let email: String
    
    public init(
        id: UUID,
        email: String
    ) {
        self.id = id
        self.email = email
    }
}

extension AuthenticatedUser: SessionAuthenticatable {
    public var sessionID: UUID { id }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/DatabaseModelInterface.swift
================================================
import Vapor
import Fluent

public protocol DatabaseModelInterface: Fluent.Model
    where Self.IDValue == UUID
{    
    associatedtype Module: ModuleInterface

    static var identifier: String { get }
}

public extension DatabaseModelInterface {

    static var schema: String {
        Module.identifier + "_" + identifier
    }
    
    static var identifier: String {
        String(describing: self)
            .dropFirst(Module.identifier.count)
            .dropLast(5)
            .lowercased() + "s"
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/AbstractForm.swift
================================================
import Vapor

open class AbstractForm: FormComponent {
    
    open var action: FormAction
    open var fields: [FormComponent]
    open var error: String?
    open var submit: String?
    
    public init(
        action: FormAction = .init(),
        fields: [FormComponent] = [],
        error: String? = nil,
        submit: String? = nil
    ) {
        self.action = action
        self.fields = fields
        self.error = error
        self.submit = submit
        
        self.action.enctype = .multipart
    }
        
    open func load(req: Request) async throws {
        for field in fields {
            try await field.load(req: req)
        }
    }
    
    open func process(req: Request) async throws {
        for field in fields {
            try await field.process(req: req)
        }
    }
    
    open func validate(req: Request) async throws -> Bool {
        var result: [Bool] = []
        for field in fields {
            result.append(try await field.validate(req: req))
        }
        return result.filter { $0 == false }.isEmpty
    }
    
    open func write(req: Request) async throws {
        for field in fields {
            try await field.write(req: req)
        }
    }
    
    open func save(req: Request) async throws {
        for field in fields {
            try await field.save(req: req)
        }
    }
    
    open func read(req: Request) async throws {
        for field in fields {
            try await field.read(req: req)
        }
    }
    
    open func render(req: Request) -> TemplateRepresentable {
        FormTemplate(
            .init(
                action: action,
                fields: fields.map { $0.render(req: req)},
                error: error,
                submit: submit
            )
        )
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/AbstractFormField.swift
================================================
import Vapor

open class AbstractFormField<
    Input: Decodable,
    Output: TemplateRepresentable
>: FormComponent {
    
    public var key: String
    public var input: Input
    public var output: Output
    public var error: String?
    
    // MARK: - event blocks
    
    public typealias FormFieldBlock =
        (Request, AbstractFormField<Input, Output>) async throws -> Void
    public typealias FormFieldValidatorsBlock =
        ((Request, AbstractFormField<Input, Output>) -> [AsyncValidator])
    
    private var readBlock: FormFieldBlock?
    private var writeBlock: FormFieldBlock?
    private var loadBlock: FormFieldBlock?
    private var saveBlock: FormFieldBlock?
    private var validatorsBlock: FormFieldValidatorsBlock?
    
    // MARK: - init & config

    public init(
        key: String,
        input: Input,
        output: Output,
        error: String? = nil
    ) {
        self.key = key
        self.input = input
        self.output = output
        self.error = error
    }

    open func config(
        _ block: (AbstractFormField<Input, Output>) -> Void
    ) -> Self {
        block(self)
        return self
    }
    
    // MARK: - Block setters
    
    open func read(_ block: @escaping FormFieldBlock) -> Self {
        readBlock = block
        return self
    }
    
    open func write(_ block: @escaping FormFieldBlock) -> Self {
        writeBlock = block
        return self
    }
    
    open func load(_ block: @escaping FormFieldBlock) -> Self {
        loadBlock = block
        return self
    }
    
    open func save(_ block: @escaping FormFieldBlock) -> Self {
        saveBlock = block
        return self
    }
    
    open func validators(
        @AsyncValidatorBuilder _ block: @escaping FormFieldValidatorsBlock
    ) -> Self {
        validatorsBlock = block
        return self
    }
    
    // MARK: - FormComponent
    
    open func process(req: Request) async throws {
        if let value = try? req.content.get(Input.self, at: key) {
            input = value
        }
    }
    
    open func validate(req: Request) async throws -> Bool {
        guard let validators = validatorsBlock else {
            return true
        }
        return await RequestValidator(validators(req, self)).isValid(req)
    }
    
    open func read(req: Request) async throws {
        try await readBlock?(req, self)
    }
    
    open func write(req: Request) async throws {
        try await writeBlock?(req, self)
    }
    
    open func load(req: Request) async throws {
        try await loadBlock?(req, self)
    }
    
    open func save(req: Request) async throws {
        try await saveBlock?(req, self)
    }

    open func render(req: Request) -> TemplateRepresentable {
        output
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Fields/InputField.swift
================================================
public final class InputField: AbstractFormField<
    String,
    InputFieldTemplate
> {

    public convenience init(_ key: String) {
        self.init(
            key: key,
            input: "",
            output: .init(
                .init(
                    key: key
                )
            )
        )
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/FormAction.swift
================================================
import SwiftHtml

public struct FormAction {
    
    public var method: SwiftHtml.Method
    public var url: String?
    public var enctype: SwiftHtml.Enctype?
    
    public init(
        method: SwiftHtml.Method = .post,
        url: String? = nil,
        enctype: SwiftHtml.Enctype? = nil
    ) {
        self.method = method
        self.url = url
        self.enctype = enctype
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/FormComponent.swift
================================================
import Vapor

public protocol FormComponent {
    
    func load(req: Request) async throws
    func process(req: Request) async throws
    func validate(req: Request) async throws -> Bool
    func write(req: Request) async throws
    func save(req: Request) async throws
    func read(req: Request) async throws
    func render(req: Request) -> TemplateRepresentable
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/FormComponentBuilder.swift
================================================
@resultBuilder
public enum FormComponentBuilder {
    
    public static func buildBlock(
        _ components: FormComponent...
    ) -> [FormComponent] {
        components
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Contexts/FormContext.swift
================================================
public struct FormContext {
    public var action: FormAction
    public var fields: [TemplateRepresentable]
    public var error: String?
    public var submit: String?
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Contexts/InputFieldContext.swift
================================================
import SwiftHtml

public struct InputFieldContext {
    
    public let key: String
    public var label: LabelContext
    public var type: SwiftHtml.Input.`Type`
    public var placeholder: String?
    public var value: String?
    public var error: String?
    
    public init(
        key: String,
        label: LabelContext? = nil,
        type: Input.`Type` = .text,
        placeholder: String? = nil,
        value: String? = nil,
        error: String? = nil
    ) {
        self.key = key
        self.label = label ?? .init(key: key)
        self.type = type
        self.placeholder = placeholder
        self.value = value
        self.error = error
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Contexts/LabelContext.swift
================================================
public struct LabelContext {
    
    public var key: String
    public var title: String?
    public var required: Bool
    public var more: String?
    
    public init(
        key: String,
        title: String? = nil,
        required: Bool = false,
        more: String? = nil
    ) {
        self.key = key
        self.title = title
        self.required = required
        self.more = more
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Html/FormTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct FormTemplate: TemplateRepresentable {
    
    var context: FormContext
    
    public init(
        _ context: FormContext
    ) {
        self.context = context
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Form {
            if let error = context.error {
                Section {
                    P(error)
                        .class("error")
                }
            }
            for field in context.fields {
                Section {
                    field.render(req)
                }
            }
            
            Section {
                Input()
                    .type(.submit)
                    .value(context.submit ?? "Save")
            }
        }
        .method(context.action.method)
        .action(context.action.url)
        .enctype(context.action.enctype)
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Html/InputFieldTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct InputFieldTemplate: TemplateRepresentable {

    public var context: InputFieldContext
    
    public init(
        _ context: InputFieldContext
    ) {
        self.context = context
    }
    
    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        LabelTemplate(context.label).render(req)

        Input()
            .type(context.type)
            .key(context.key)
            .placeholder(context.placeholder)
            .value(context.value)
            .class("field")
        if let error = context.error {
            Span(error)
                .class("error")
        }
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Form/Templates/Html/LabelTemplate.swift
================================================
import Vapor
import SwiftHtml

public struct LabelTemplate: TemplateRepresentable {

    var context: LabelContext

    public init(
        _ context: LabelContext
    ) {
        self.context = context
    }

    @TagBuilder
    public func render(
        _ req: Request
    ) -> Tag {
        Label {
            Text(context.title ?? context.key.capitalized)

            if let more = context.more {
                Span(more)
                    .class("more")
            }
            if context.required {
                Span("*")
                    .class("required")
            }
        }.for(context.key)
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/ModuleInterface.swift
================================================
import Vapor

public protocol ModuleInterface {
    
    static var identifier: String { get }

    func boot(_ app: Application) throws
}

public extension ModuleInterface {

    func boot(_ app: Application) throws {}

    static var identifier: String {
        String(describing: self).dropLast(6).lowercased()
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/AsyncValidator.swift
================================================
import Vapor

public protocol AsyncValidator {
    
    var key: String { get }
    var message: String { get }

    func validate(
        _ req: Request
    ) async throws -> ValidationErrorDetail?
}

public extension AsyncValidator {

    var error: ValidationErrorDetail {
        .init(key: key, message: message)
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/AsyncValidatorBuilder.swift
================================================
@resultBuilder
public enum AsyncValidatorBuilder {
    
    public static func buildBlock(
        _ components: AsyncValidator...
    ) -> [AsyncValidator] {
        components
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/FormFieldValidator+Validations.swift
================================================
import Vapor

public extension FormFieldValidator where Input == String {

    static func required(
        _ field: AbstractFormField<Input, Output>,
        _ message: String? = nil
    ) -> FormFieldValidator<Input, Output> {
        let msg = message ??
            "\(field.key.capitalized) is required"
        return .init(field, msg) { _, field in
            !field.input.isEmpty
        }
    }
    
    static func min(
        _ field: AbstractFormField<Input, Output>,
        length: Int,
        message: String? = nil
    ) -> FormFieldValidator<Input, Output> {
        let msg = message ??
            "\(field.key.capitalized) is too short (min: \(length) characters)"
        return .init(field, msg) { _, field in
            field.input.count >= length
        }
    }
    
    static func max(
        _ field: AbstractFormField<Input, Output>,
        length: Int,
        message: String? = nil
    ) -> FormFieldValidator<Input, Output> {
        let msg = message ??
            "\(field.key.capitalized) is too short (min: \(length) characters)"
        return .init(field, msg) { _, field in
            field.input.count <= length
        }
    }

    static func alphanumeric(
        _ field: AbstractFormField<Input, Output>,
        message: String? = nil
    ) -> FormFieldValidator<Input, Output> {
        let msg = message ??
            "\(field.key.capitalized) should be only alphanumeric characters"
        return .init(field, msg) { _, field in
            !Validator
                .characterSet(.alphanumerics)
                .validate(field.input)
                .isFailure
        }
    }

    static func email(
        _ field: AbstractFormField<Input, Output>,
        message: String? = nil
    ) -> FormFieldValidator<Input, Output> {
        let msg = message ??
            "\(field.key.capitalized) should be a valid email address"
        return .init(field, msg) { _, field in
            !Validator
                .email
                .validate(field.input)
                .isFailure
        }
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/FormFieldValidator.swift
================================================
import Vapor

public struct FormFieldValidator<
    Input: Decodable,
    Output: TemplateRepresentable
>: AsyncValidator {
    
    public typealias AsyncValidationBlock =
        ((Request, AbstractFormField<Input, Output>) async throws -> Bool)

    public let field: AbstractFormField<Input, Output>
    public let message: String
    public let validation: AsyncValidationBlock
    
    public var key: String { field.key }
    
    public init(
        _ field: AbstractFormField<Input, Output>,
        _ message: String,
        _ validation: @escaping AsyncValidationBlock
    ) {
        self.field = field
        self.message = message
        self.validation = validation
    }
    
    public func validate(
        _ req: Request
    ) async throws -> ValidationErrorDetail? {
        let isValid = try await validation(req, field)
        if isValid {
            return nil
        }
        field.error = message
        return error
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/RequestValidator.swift
================================================
import Vapor

public struct RequestValidator {

    public var validators: [AsyncValidator]
    
    public init(
        _ validators: [AsyncValidator]
    ) {
        self.validators = validators
    }

    public func validate(
        _ req: Request,
        message: String? = nil
    ) async throws {
        var result: [ValidationErrorDetail] = []
        for validator in validators {
            if result.contains(where: { $0.key == validator.key }) {
                continue
            }
            if let res = try await validator.validate(req) {
                result.append(res)
            }
        }
        if !result.isEmpty {
            throw ValidationAbort(
                abort: Abort(.badRequest, reason: message),
                details: result
            )
        }
    }

    public func isValid(
        _ req: Request
    ) async -> Bool {
        do {
            try await validate(req, message: nil)
            return true
        }
        catch {
            return false
        }
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/ValidationAbort.swift
================================================
import Vapor

public struct ValidationAbort: AbortError {

    public var abort: Abort
    public var message: String?
    public var details: [ValidationErrorDetail]

    public var reason: String { abort.reason }
    public var status: HTTPStatus { abort.status }
    
    public init(
        abort: Abort,
        message: String? = nil,
        details: [ValidationErrorDetail]
    ) {
        self.abort = abort
        self.message = message
        self.details = details
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Framework/Validation/ValidationErrorDetail.swift
================================================
import Vapor

public struct ValidationErrorDetail: Codable {

    public var key: String
    public var message: String
    
    public init(
        key: String,
        message: String
    ) {
        self.key = key
        self.message = message
    }
}

extension ValidationErrorDetail: Content {}


================================================
FILE: Chapter 07/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift
================================================
import Vapor

struct ExtendPathMiddleware: AsyncMiddleware {

    func respond(
        to req: Request,
        chainingTo next: AsyncResponder
    ) async throws -> Response {
        if !req.url.path.hasSuffix("/") && !req.url.path.contains(".") {
            return req.redirect(
                to: req.url.path + "/",
                redirectType: .permanent
            )
        }
        return try await next.respond(
            to: req
        )
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Modules/Blog/BlogModule.swift
================================================
import Vapor

struct BlogModule: ModuleInterface {

    let router = BlogRouter()

    func boot(_ app: Application) throws {
        app.migrations.add(BlogMigrations.v1())
        app.migrations.add(BlogMigrations.seed())
        
        try router.boot(routes: app.routes)
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Modules/Blog/BlogRouter.swift
================================================
import Vapor

struct BlogRouter: RouteCollection {
    
    let controller = BlogFrontendController()
    
    func boot(
        routes: RoutesBuilder
    ) throws {
        routes.get("blog", use: controller.blogView)
        routes.get(.anything, use: controller.postView)
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift
================================================
import Vapor
import Fluent

struct BlogFrontendController {
    
    func blogView(req: Request) async throws -> Response {
        let postModels = try await BlogPostModel
            .query(on: req.db)
            .sort(\.$date, .descending)
            .all()

        let posts = try postModels.map {
            Blog.Post.List(
                id: try $0.requireID(),
                title: $0.title,
                slug: $0.slug,
                image: $0.imageKey,
                excerpt: $0.excerpt,
                date: $0.date
            )
        }

        let ctx = BlogPostsContext(
            icon: "🔥",
            title: "Blog",
            message: "Hot news and stories about everything.",
            posts: posts)

        return req.templates.renderHtml(
            BlogPostsTemplate(ctx)
        )
    }

    func postView(req: Request) async throws -> Response {
        let slug = req.url.path.trimmingCharacters(
            in: .init(charactersIn: "/")
        )
        guard
            let post = try await BlogPostModel
                .query(on: req.db)
                .filter(\.$slug == slug)
                .with(\.$category)
                .first()
        else {
            return req.redirect(to: "/")
        }
        let ctx = BlogPostContext(
            post: Blog.Post.Detail(
                id: post.id!,
                title: post.title,
                slug: post.slug,
                image: post.imageKey,
                excerpt: post.excerpt,
                date: post.date,
                category: .init(
                    id: post.category.id!,
                    title: post.category.title
                ),
                content: post.content
            )
        )

        return req.templates.renderHtml(
            BlogPostTemplate(ctx)
        )
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift
================================================
import Foundation
import Fluent

enum BlogMigrations {
    
    struct v1: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            try await db.schema(BlogCategoryModel.schema)
                .id()
                .field(BlogCategoryModel.FieldKeys.v1.title, .string, .required)
                .create()
            
            try await db.schema(BlogPostModel.schema)
                .id()
                .field(BlogPostModel.FieldKeys.v1.title, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.slug, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.imageKey, .string, .required)
                .field(BlogPostModel.FieldKeys.v1.excerpt, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.date, .datetime, .required)
                .field(BlogPostModel.FieldKeys.v1.content, .data, .required)
                .field(BlogPostModel.FieldKeys.v1.categoryId, .uuid)
                .foreignKey(
                    BlogPostModel.FieldKeys.v1.categoryId,
                    references: BlogCategoryModel.schema, .id,
                    onDelete: DatabaseSchema.ForeignKeyAction.setNull,
                    onUpdate: .cascade
                )
                .unique(on: BlogPostModel.FieldKeys.v1.slug)
                .create()
        }
        
        func revert(on db: Database) async throws  {
            try await db.schema(BlogCategoryModel.schema).delete()
            try await db.schema(BlogPostModel.schema).delete()
        }
    }
    
    struct seed: AsyncMigration {
        
        func prepare(on db: Database) async throws {
            
            let categories = (1...4).map { index in
                BlogCategoryModel(title: "Sample category #\(index)")
            }
            try await categories.create(on: db)
            
            try await (1...9).map { index in
                BlogPostModel(
                    id: nil, title: "Sample post #\(index)",
                    slug: "sample-post-\(index)",
                    imageKey: "/img/posts/\(String(format: "%02d", index + 1)).jpg",
                    excerpt: "Lorem ipsum",
                    date: Date().addingTimeInterval(-Double.random(in: 0...(86400 * 60))),
                    content: "Lorem ipsum dolor sit amet.",
                    categoryId: categories[Int.random(in: 0..<categories.count)].id!
                )
            }
            .create(on: db)
        }
        
        func revert(on db: Database) async throws {
            try await BlogPostModel.query(on: db).delete()
            try await BlogCategoryModel.query(on: db).delete()
        }
    }
}


================================================
FILE: Chapter 07/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift
================================================
import Vapor
import Fluent

final class BlogCategoryModel: DatabaseModelInterface {
    typealias Module = BlogModule

    static let identifier = "categories"
    
    struct FieldKeys {
        struct v1 {
            static var title: FieldKey { "title" }
        }
    }
    
    @ID() var id: UUID?
    
    @Field(key: FieldKeys.v1.title)
    var title: String
    
    @Children(for: \.$category)
    var posts: [BlogPostModel]
    
    init() { }
    
    init(
        id: UUID? = nil,
        title: String
Download .txt
gitextract_ovv508se/

├── .gitignore
├── Changelog.md
├── Chapter 01/
│   └── .gitkeep
├── Chapter 02/
│   ├── SPM/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   └── Sources/
│   │       └── main.swift
│   └── VaporToolbox/
│       ├── .dockerignore
│       ├── .gitignore
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   └── .gitkeep
│       ├── README.md
│       ├── Sources/
│       │   └── myProject/
│       │       ├── Controllers/
│       │       │   └── .gitkeep
│       │       ├── configure.swift
│       │       ├── entrypoint.swift
│       │       └── routes.swift
│       ├── Tests/
│       │   └── myProjectTests/
│       │       └── myProjectTests.swift
│       └── docker-compose.yml
├── Chapter 03/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   └── myProject/
│       │       ├── Middlewares/
│       │       │   └── ExtendPathMiddleware.swift
│       │       ├── Modules/
│       │       │   ├── Blog/
│       │       │   │   ├── BlogPost.swift
│       │       │   │   ├── BlogRouter.swift
│       │       │   │   ├── Controllers/
│       │       │   │   │   └── BlogFrontendController.swift
│       │       │   │   └── Templates/
│       │       │   │       ├── Contexts/
│       │       │   │       │   ├── BlogPostContext.swift
│       │       │   │       │   └── BlogPostsContext.swift
│       │       │   │       └── Html/
│       │       │   │           ├── BlogPostTemplate.swift
│       │       │   │           └── BlogPostsTemplate.swift
│       │       │   └── Web/
│       │       │       ├── Controllers/
│       │       │       │   └── WebFrontendController.swift
│       │       │       ├── Templates/
│       │       │       │   ├── Contexts/
│       │       │       │   │   ├── WebHomeContext.swift
│       │       │       │   │   ├── WebIndexContext.swift
│       │       │       │   │   └── WebLinkContext.swift
│       │       │       │   └── Html/
│       │       │       │       ├── WebHomeTemplate.swift
│       │       │       │       ├── WebIndexTemplate.swift
│       │       │       │       └── WebLinkTemplate.swift
│       │       │       └── WebRouter.swift
│       │       ├── Template/
│       │       │   ├── Request+Template.swift
│       │       │   ├── TemplateRenderer.swift
│       │       │   └── TemplateRepresentable.swift
│       │       ├── configure.swift
│       │       ├── entrypoint.swift
│       │       └── routes.swift
│       ├── Tests/
│       │   └── myProjectTests/
│       │       └── myProjectTests.swift
│       └── docker-compose.yml
├── Chapter 04/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 05/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 06/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   └── InputField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   └── LabelContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           └── LabelTemplate.swift
│       │   │   │   └── ModuleInterface.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 07/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   └── InputField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   └── LabelContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           └── LabelTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 08/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   └── web.css
│       │   └── js/
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   ├── FormImageInput.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── HiddenFieldContext.swift
│       │   │   │   │       │   ├── ImageFieldContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   ├── LabelContext.swift
│       │   │   │   │       │   ├── OptionContext.swift
│       │   │   │   │       │   ├── SelectFieldContext.swift
│       │   │   │   │       │   └── TextareaFieldContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── HiddenFieldTemplate.swift
│       │   │   │   │           ├── ImageFieldTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           ├── LabelTemplate.swift
│       │   │   │   │           ├── SelectFieldTemplate.swift
│       │   │   │   │           └── TextareaFieldTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── BlogFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 09/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   ├── FormImageInput.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── FormContext.swift
│       │   │   │   │       │   ├── HiddenFieldContext.swift
│       │   │   │   │       │   ├── ImageFieldContext.swift
│       │   │   │   │       │   ├── InputFieldContext.swift
│       │   │   │   │       │   ├── LabelContext.swift
│       │   │   │   │       │   ├── OptionContext.swift
│       │   │   │   │       │   ├── SelectFieldContext.swift
│       │   │   │   │       │   └── TextareaFieldContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── FormTemplate.swift
│       │   │   │   │           ├── HiddenFieldTemplate.swift
│       │   │   │   │           ├── ImageFieldTemplate.swift
│       │   │   │   │           ├── InputFieldTemplate.swift
│       │   │   │   │           ├── LabelTemplate.swift
│       │   │   │   │           ├── SelectFieldTemplate.swift
│       │   │   │   │           └── TextareaFieldTemplate.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── AdminFrontendController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   └── AdminIndexContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           └── AdminIndexTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 10/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   └── ModelController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 11/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   └── ModelController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 12/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   └── Controllers/
│       │   │   │   │       ├── ApiController.swift
│       │   │   │   │       ├── ApiCreateController.swift
│       │   │   │   │       ├── ApiDeleteController.swift
│       │   │   │   │       ├── ApiDetailController.swift
│       │   │   │   │       ├── ApiListController.swift
│       │   │   │   │       ├── ApiPatchController.swift
│       │   │   │   │       └── ApiUpdateController.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   └── UserSessionAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       └── UserAccountModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 13/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       └── AppTests.swift
│       └── docker-compose.yml
├── Chapter 14/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
├── Chapter 15/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   ├── ApiModuleInterface.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── Hooks/
│       │   │   │   │   ├── Application+HookStorage.swift
│       │   │   │   │   ├── Async/
│       │   │   │   │   │   ├── Application+AsyncHooks.swift
│       │   │   │   │   │   ├── AsyncAnyHookFunction.swift
│       │   │   │   │   │   ├── AsyncHookFunction.swift
│       │   │   │   │   │   ├── HookStorage+AsyncHooks.swift
│       │   │   │   │   │   └── Request+AsyncHooks.swift
│       │   │   │   │   ├── HookArguments.swift
│       │   │   │   │   ├── HookFunctionPointer.swift
│       │   │   │   │   ├── HookStorage.swift
│       │   │   │   │   └── Sync/
│       │   │   │   │       ├── AnyHookFunction.swift
│       │   │   │   │       ├── Application+Hooks.swift
│       │   │   │   │       ├── HookFunction.swift
│       │   │   │   │       ├── HookStorage+Hooks.swift
│       │   │   │   │       └── Request+Hooks.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── ApiRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── Blog.swift
│       │   │   │   │   │   ├── BlogCategory.swift
│       │   │   │   │   │   └── BlogPost.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogAdminWidgetTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Objects/
│       │   │   │   │   │   ├── User.swift
│       │   │   │   │   │   ├── UserAccount.swift
│       │   │   │   │   │   └── UserToken.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
├── Chapter 16/
│   └── myProject/
│       ├── Dockerfile
│       ├── Package.swift
│       ├── Public/
│       │   ├── css/
│       │   │   ├── admin.css
│       │   │   └── web.css
│       │   └── js/
│       │       ├── admin.js
│       │       └── web.js
│       ├── Sources/
│       │   ├── App/
│       │   │   ├── Extensions/
│       │   │   │   └── Svg+MenuIcon.swift
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface+PathComponent.swift
│       │   │   │   ├── AuthenticatedUser.swift
│       │   │   │   ├── Controllers/
│       │   │   │   │   ├── CreateController.swift
│       │   │   │   │   ├── DeleteController.swift
│       │   │   │   │   ├── DetailController.swift
│       │   │   │   │   ├── ListController.swift
│       │   │   │   │   ├── ModelController.swift
│       │   │   │   │   ├── PatchController.swift
│       │   │   │   │   └── UpdateController.swift
│       │   │   │   ├── DatabaseModelInterface.swift
│       │   │   │   ├── Extensions/
│       │   │   │   │   ├── ByteBuffer+Data.swift
│       │   │   │   │   └── File+ByteBuffer.swift
│       │   │   │   ├── Form/
│       │   │   │   │   ├── AbstractForm.swift
│       │   │   │   │   ├── AbstractFormField.swift
│       │   │   │   │   ├── Fields/
│       │   │   │   │   │   ├── HiddenField.swift
│       │   │   │   │   │   ├── ImageField.swift
│       │   │   │   │   │   ├── InputField.swift
│       │   │   │   │   │   ├── SelectField.swift
│       │   │   │   │   │   └── TextareaField.swift
│       │   │   │   │   ├── FormAction.swift
│       │   │   │   │   ├── FormComponent.swift
│       │   │   │   │   ├── FormComponentBuilder.swift
│       │   │   │   │   ├── FormImageData.swift
│       │   │   │   │   └── FormImageInput.swift
│       │   │   │   ├── Hooks/
│       │   │   │   │   ├── Application+HookStorage.swift
│       │   │   │   │   ├── Async/
│       │   │   │   │   │   ├── Application+AsyncHooks.swift
│       │   │   │   │   │   ├── AsyncAnyHookFunction.swift
│       │   │   │   │   │   ├── AsyncHookFunction.swift
│       │   │   │   │   │   ├── HookStorage+AsyncHooks.swift
│       │   │   │   │   │   └── Request+AsyncHooks.swift
│       │   │   │   │   ├── HookArguments.swift
│       │   │   │   │   ├── HookFunctionPointer.swift
│       │   │   │   │   ├── HookStorage.swift
│       │   │   │   │   └── Sync/
│       │   │   │   │       ├── AnyHookFunction.swift
│       │   │   │   │       ├── Application+Hooks.swift
│       │   │   │   │       ├── HookFunction.swift
│       │   │   │   │       ├── HookStorage+Hooks.swift
│       │   │   │   │       └── Request+Hooks.swift
│       │   │   │   ├── ModelEditorInterface.swift
│       │   │   │   ├── ModuleInterface.swift
│       │   │   │   ├── Templates/
│       │   │   │   │   ├── Contexts/
│       │   │   │   │   │   ├── CellContext.swift
│       │   │   │   │   │   ├── ColumnContext.swift
│       │   │   │   │   │   ├── DetailContext.swift
│       │   │   │   │   │   ├── FormContext.swift
│       │   │   │   │   │   ├── HiddenFieldContext.swift
│       │   │   │   │   │   ├── ImageFieldContext.swift
│       │   │   │   │   │   ├── InputFieldContext.swift
│       │   │   │   │   │   ├── LabelContext.swift
│       │   │   │   │   │   ├── LinkContext.swift
│       │   │   │   │   │   ├── OptionContext.swift
│       │   │   │   │   │   ├── RowContext.swift
│       │   │   │   │   │   ├── SelectFieldContext.swift
│       │   │   │   │   │   ├── TableContext.swift
│       │   │   │   │   │   └── TextareaFieldContext.swift
│       │   │   │   │   └── Html/
│       │   │   │   │       ├── CellTemplate.swift
│       │   │   │   │       ├── DetailTemplate.swift
│       │   │   │   │       ├── FormTemplate.swift
│       │   │   │   │       ├── HiddenFieldTemplate.swift
│       │   │   │   │       ├── ImageFieldTemplate.swift
│       │   │   │   │       ├── InputFieldTemplate.swift
│       │   │   │   │       ├── LabelTemplate.swift
│       │   │   │   │       ├── LinkTemplate.swift
│       │   │   │   │       ├── SelectFieldTemplate.swift
│       │   │   │   │       ├── TableTemplate.swift
│       │   │   │   │       └── TextareaFieldTemplate.swift
│       │   │   │   └── Validation/
│       │   │   │       ├── AsyncValidator.swift
│       │   │   │       ├── AsyncValidatorBuilder.swift
│       │   │   │       ├── FormFieldValidator+Validations.swift
│       │   │   │       ├── FormFieldValidator.swift
│       │   │   │       ├── KeyedContentValidator+Validations.swift
│       │   │   │       ├── KeyedContentValidator.swift
│       │   │   │       ├── RequestValidator.swift
│       │   │   │       ├── ValidationAbort.swift
│       │   │   │       ├── ValidationError.swift
│       │   │   │       └── ValidationErrorDetail.swift
│       │   │   ├── Middlewares/
│       │   │   │   └── ExtendPathMiddleware.swift
│       │   │   ├── Modules/
│       │   │   │   ├── Admin/
│       │   │   │   │   ├── AdminModule.swift
│       │   │   │   │   ├── AdminRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── AdminController.swift
│       │   │   │   │   │   ├── AdminCreateController.swift
│       │   │   │   │   │   ├── AdminDeleteController.swift
│       │   │   │   │   │   ├── AdminDetailController.swift
│       │   │   │   │   │   ├── AdminFrontendController.swift
│       │   │   │   │   │   ├── AdminListController.swift
│       │   │   │   │   │   └── AdminUpdateController.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── AdminDashboardContext.swift
│       │   │   │   │       │   ├── AdminDeletePageContext.swift
│       │   │   │   │       │   ├── AdminDetailPageContext.swift
│       │   │   │   │       │   ├── AdminEditorPageContext.swift
│       │   │   │   │       │   ├── AdminIndexContext.swift
│       │   │   │   │       │   └── AdminListPageContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── AdminDashboardTemplate.swift
│       │   │   │   │           ├── AdminDeletePageTemplate.swift
│       │   │   │   │           ├── AdminDetailPageTemplate.swift
│       │   │   │   │           ├── AdminEditorPageTemplate.swift
│       │   │   │   │           ├── AdminIndexTemplate.swift
│       │   │   │   │           └── AdminListPageTemplate.swift
│       │   │   │   ├── Api/
│       │   │   │   │   ├── ApiModule.swift
│       │   │   │   │   ├── ApiRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── ApiController.swift
│       │   │   │   │   │   ├── ApiCreateController.swift
│       │   │   │   │   │   ├── ApiDeleteController.swift
│       │   │   │   │   │   ├── ApiDetailController.swift
│       │   │   │   │   │   ├── ApiListController.swift
│       │   │   │   │   │   ├── ApiPatchController.swift
│       │   │   │   │   │   └── ApiUpdateController.swift
│       │   │   │   │   └── Middlewares/
│       │   │   │   │       └── ApiErrorMiddleware.swift
│       │   │   │   ├── Blog/
│       │   │   │   │   ├── BlogModule.swift
│       │   │   │   │   ├── BlogRouter.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── BlogCategoryAdminController.swift
│       │   │   │   │   │   ├── BlogCategoryApiController.swift
│       │   │   │   │   │   ├── BlogFrontendController.swift
│       │   │   │   │   │   ├── BlogPostAdminController.swift
│       │   │   │   │   │   └── BlogPostApiController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── BlogMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── BlogCategoryModel.swift
│       │   │   │   │   │       └── BlogPostModel.swift
│       │   │   │   │   ├── Editors/
│       │   │   │   │   │   ├── BlogCategoryEditor.swift
│       │   │   │   │   │   └── BlogPostEditor.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── BlogPostEditForm.swift
│       │   │   │   │   └── Templates/
│       │   │   │   │       ├── Contexts/
│       │   │   │   │       │   ├── BlogPostAdminDeleteContext.swift
│       │   │   │   │       │   ├── BlogPostAdminDetailContext.swift
│       │   │   │   │       │   ├── BlogPostAdminEditContext.swift
│       │   │   │   │       │   ├── BlogPostAdminListContext.swift
│       │   │   │   │       │   ├── BlogPostContext.swift
│       │   │   │   │       │   └── BlogPostsContext.swift
│       │   │   │   │       └── Html/
│       │   │   │   │           ├── BlogAdminWidgetTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDeleteTemplate.swift
│       │   │   │   │           ├── BlogPostAdminDetailTemplate.swift
│       │   │   │   │           ├── BlogPostAdminEditTemplate.swift
│       │   │   │   │           ├── BlogPostAdminListTemplate.swift
│       │   │   │   │           ├── BlogPostTemplate.swift
│       │   │   │   │           └── BlogPostsTemplate.swift
│       │   │   │   ├── User/
│       │   │   │   │   ├── Authenticators/
│       │   │   │   │   │   ├── UserCredentialsAuthenticator.swift
│       │   │   │   │   │   ├── UserSessionAuthenticator.swift
│       │   │   │   │   │   └── UserTokenAuthenticator.swift
│       │   │   │   │   ├── Controllers/
│       │   │   │   │   │   ├── UserApiController.swift
│       │   │   │   │   │   └── UserFrontendController.swift
│       │   │   │   │   ├── Database/
│       │   │   │   │   │   ├── Migrations/
│       │   │   │   │   │   │   └── UserMigrations.swift
│       │   │   │   │   │   └── Models/
│       │   │   │   │   │       ├── UserAccountModel.swift
│       │   │   │   │   │       └── UserTokenModel.swift
│       │   │   │   │   ├── Forms/
│       │   │   │   │   │   └── UserLoginForm.swift
│       │   │   │   │   ├── Templates/
│       │   │   │   │   │   ├── Contexts/
│       │   │   │   │   │   │   └── UserLoginContext.swift
│       │   │   │   │   │   └── Html/
│       │   │   │   │   │       └── UserLoginTemplate.swift
│       │   │   │   │   ├── UserModule.swift
│       │   │   │   │   └── UserRouter.swift
│       │   │   │   └── Web/
│       │   │   │       ├── Controllers/
│       │   │   │       │   └── WebFrontendController.swift
│       │   │   │       ├── Templates/
│       │   │   │       │   ├── Contexts/
│       │   │   │       │   │   ├── WebHomeContext.swift
│       │   │   │       │   │   ├── WebIndexContext.swift
│       │   │   │       │   │   └── WebLinkContext.swift
│       │   │   │       │   └── Html/
│       │   │   │       │       ├── WebHomeTemplate.swift
│       │   │   │       │       ├── WebIndexTemplate.swift
│       │   │   │       │       └── WebLinkTemplate.swift
│       │   │   │       ├── WebModule.swift
│       │   │   │       └── WebRouter.swift
│       │   │   ├── Template/
│       │   │   │   ├── Request+Template.swift
│       │   │   │   ├── TemplateRenderer.swift
│       │   │   │   └── TemplateRepresentable.swift
│       │   │   ├── configure.swift
│       │   │   └── routes.swift
│       │   ├── AppApi/
│       │   │   ├── Framework/
│       │   │   │   ├── ApiModelInterface.swift
│       │   │   │   └── ApiModuleInterface.swift
│       │   │   └── Modules/
│       │   │       ├── Blog/
│       │   │       │   ├── Blog.swift
│       │   │       │   ├── BlogCategory.swift
│       │   │       │   └── BlogPost.swift
│       │   │       └── User/
│       │   │           ├── User.swift
│       │   │           ├── UserAccount.swift
│       │   │           └── UserToken.swift
│       │   └── Run/
│       │       └── main.swift
│       ├── Tests/
│       │   ├── AppApiTests/
│       │   │   └── AppApiTests.swift
│       │   └── AppTests/
│       │       ├── AppTests.swift
│       │       ├── BlogCategoryApiTests.swift
│       │       ├── BlogPostApiTests.swift
│       │       └── Framework/
│       │           ├── AppTestCase.swift
│       │           └── XCTApplicationTester.swift
│       └── docker-compose.yml
└── README.md
Download .txt
SYMBOL INDEX (14 symbols across 14 files)

FILE: Chapter 03/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 04/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 05/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 06/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 07/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 08/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 09/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 10/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 11/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 12/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 13/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 14/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 15/myProject/Public/js/web.js
  function about (line 1) | function about() {

FILE: Chapter 16/myProject/Public/js/web.js
  function about (line 1) | function about() {
Condensed preview — 1555 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,567K chars).
[
  {
    "path": ".gitignore",
    "chars": 106,
    "preview": ".DS_Store\n.build\n.swiftpm\nxcuserdata\nMyProject.xcodeproj\nPackages\nDerivedData/\ndb.sqlite\nPackage.resolved\n"
  },
  {
    "path": "Changelog.md",
    "chars": 1988,
    "preview": "## Changelog - Practical Server Side Swift\n\nDon't forget to check the sample code repository on [GitHub](https://github."
  },
  {
    "path": "Chapter 01/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter 02/SPM/.gitignore",
    "chars": 53,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n"
  },
  {
    "path": "Chapter 02/SPM/Package.swift",
    "chars": 517,
    "preview": "// swift-tools-version:6.1\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 02/SPM/README.md",
    "chars": 44,
    "preview": "# myProject\n\nA description of this package.\n"
  },
  {
    "path": "Chapter 02/SPM/Sources/main.swift",
    "chars": 256,
    "preview": "import Vapor\n\nlet env = try Environment.detect()\nlet app = try await Application.make(env)\n\ndo {\n    app.get { req in \"H"
  },
  {
    "path": "Chapter 02/VaporToolbox/.dockerignore",
    "chars": 18,
    "preview": ".build/\n.swiftpm/\n"
  },
  {
    "path": "Chapter 02/VaporToolbox/.gitignore",
    "chars": 106,
    "preview": "Packages\n.build\nxcuserdata\n*.xcodeproj\nDerivedData/\n.DS_Store\ndb.sqlite\n.swiftpm\n.env\n.env.*\n!.env.example"
  },
  {
    "path": "Chapter 02/VaporToolbox/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 02/VaporToolbox/Package.swift",
    "chars": 1039,
    "preview": "// swift-tools-version:6.0\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 02/VaporToolbox/Public/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter 02/VaporToolbox/README.md",
    "chars": 595,
    "preview": "# myProject\n\n💧 A project built with the Vapor web framework.\n\n## Getting Started\n\nTo build the project using the Swift P"
  },
  {
    "path": "Chapter 02/VaporToolbox/Sources/myProject/Controllers/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter 02/VaporToolbox/Sources/myProject/configure.swift",
    "chars": 290,
    "preview": "import Vapor\n\n// configures your application\npublic func configure(_ app: Application) async throws {\n    // uncomment t"
  },
  {
    "path": "Chapter 02/VaporToolbox/Sources/myProject/entrypoint.swift",
    "chars": 553,
    "preview": "import Vapor\nimport Logging\nimport NIOCore\nimport NIOPosix\n\n@main\nenum Entrypoint {\n\n    static func main() async throws"
  },
  {
    "path": "Chapter 02/VaporToolbox/Sources/myProject/routes.swift",
    "chars": 187,
    "preview": "import Vapor\n\nfunc routes(_ app: Application) throws {\n    app.get { req async in\n        \"It works!\"\n    }\n\n    app.get"
  },
  {
    "path": "Chapter 02/VaporToolbox/Tests/myProjectTests/myProjectTests.swift",
    "chars": 460,
    "preview": "@testable import myProject\nimport VaporTesting\nimport Testing\n\n@Suite(\"App Tests\")\nstruct myProjectTests {\n\n    @Test(\"T"
  },
  {
    "path": "Chapter 02/VaporToolbox/docker-compose.yml",
    "chars": 854,
    "preview": "# Docker Compose file for Vapor\n#\n# Install Docker on your system to run and test\n# your Vapor app in a production-like "
  },
  {
    "path": "Chapter 03/myProject/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 03/myProject/Package.swift",
    "chars": 1049,
    "preview": "// swift-tools-version:6.0\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 03/myProject/Public/css/web.css",
    "chars": 35,
    "preview": "#blog h2 {\n    margin: 0.5rem 0;\n}\n"
  },
  {
    "path": "Chapter 03/myProject/Public/js/web.js",
    "chars": 59,
    "preview": "function about() {\n    alert(\"myPage\\n\\nversion 1.0.0\");\n}\n"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Middlewares/ExtendPathMiddleware.swift",
    "chars": 470,
    "preview": "import Vapor\n\nstruct ExtendPathMiddleware: AsyncMiddleware {\n\n    func respond(\n        to req: Request,\n        chainin"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/BlogPost.swift",
    "chars": 206,
    "preview": "import Foundation\n\nstruct BlogPost: Codable {\n    let title: String\n    let slug: String\n    let image: String\n    let e"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/BlogRouter.swift",
    "chars": 288,
    "preview": "import Vapor\n\nstruct BlogRouter: RouteCollection {\n    \n    let controller = BlogFrontendController()\n    \n    func boot"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/Controllers/BlogFrontendController.swift",
    "chars": 1425,
    "preview": "import Vapor\n\nstruct BlogFrontendController {\n    \n    var posts: [BlogPost] = {\n        stride(from: 1, to: 9, by: 1).m"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Contexts/BlogPostContext.swift",
    "chars": 50,
    "preview": "struct BlogPostContext {\n    let post: BlogPost\n}\n"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Contexts/BlogPostsContext.swift",
    "chars": 121,
    "preview": "struct BlogPostsContext {\n    let icon: String\n    let title: String\n    let message: String\n    let posts: [BlogPost]\n}"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Html/BlogPostTemplate.swift",
    "chars": 1128,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostTemplate: TemplateRepresentable {\n\n    var context: BlogPostContext\n    \n "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Blog/Templates/Html/BlogPostsTemplate.swift",
    "chars": 1134,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostsTemplate: TemplateRepresentable {\n    \n    var context: BlogPostsContext\n"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Controllers/WebFrontendController.swift",
    "chars": 812,
    "preview": "import Vapor\n\nstruct WebFrontendController {\n    \n    func homeView(req: Request) throws -> Response {\n        let ctx ="
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebHomeContext.swift",
    "chars": 151,
    "preview": "struct WebHomeContext {\n    let icon: String\n    let title: String\n    let message: String\n    let paragraphs: [String]\n"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebIndexContext.swift",
    "chars": 153,
    "preview": "public struct WebIndexContext {\n    \n    public let title: String\n    \n    public init(\n        title: String\n    ) {\n  "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Contexts/WebLinkContext.swift",
    "chars": 223,
    "preview": "public struct WebLinkContext {\n    \n    public let label: String\n    public let url: String\n    \n    public init(\n      "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebHomeTemplate.swift",
    "chars": 836,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebHomeTemplate: TemplateRepresentable {\n\n    var context: WebHomeContext\n    \n   "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebIndexTemplate.swift",
    "chars": 3886,
    "preview": "import Vapor\nimport SwiftHtml\nimport SwiftSvg\n\nextension Svg {\n    \n    static func menuIcon() -> Svg {\n        Svg {\n  "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/Templates/Html/WebLinkTemplate.swift",
    "chars": 342,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebLinkTemplate: TemplateRepresentable {\n\n    var context: WebLinkContext\n    \n   "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Modules/Web/WebRouter.swift",
    "chars": 234,
    "preview": "import Vapor\n\nstruct WebRouter: RouteCollection {\n    \n    let frontendController = WebFrontendController()\n\n    func bo"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Template/Request+Template.swift",
    "chars": 95,
    "preview": "import Vapor\n\npublic extension Request {\n    var templates: TemplateRenderer { .init(self) }\n}\n"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Template/TemplateRenderer.swift",
    "chars": 691,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct TemplateRenderer {\n    \n    var req: Request\n    \n    init(_ req: Request) "
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/Template/TemplateRepresentable.swift",
    "chars": 133,
    "preview": "import Vapor\nimport SwiftSgml\n\npublic protocol TemplateRepresentable {\n    \n    @TagBuilder\n    func render(_ req: Reque"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/configure.swift",
    "chars": 422,
    "preview": "import Vapor\n\npublic func configure(\n    _ app: Application\n) async throws {\n\n    app.middleware.use(\n        FileMiddle"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/entrypoint.swift",
    "chars": 522,
    "preview": "import Vapor\nimport Logging\n\n@main\nenum Entrypoint {\n\n    static func main() async throws {\n        var env = try Enviro"
  },
  {
    "path": "Chapter 03/myProject/Sources/myProject/routes.swift",
    "chars": 361,
    "preview": "import Vapor\nimport SwiftHtml\n\nfunc routes(_ app: Application) throws {\n\n    app.routes.get { req -> Response in\n       "
  },
  {
    "path": "Chapter 03/myProject/Tests/myProjectTests/myProjectTests.swift",
    "chars": 460,
    "preview": "@testable import myProject\nimport VaporTesting\nimport Testing\n\n@Suite(\"App Tests\")\nstruct myProjectTests {\n\n    @Test(\"T"
  },
  {
    "path": "Chapter 03/myProject/docker-compose.yml",
    "chars": 854,
    "preview": "# Docker Compose file for Vapor\n#\n# Install Docker on your system to run and test\n# your Vapor app in a production-like "
  },
  {
    "path": "Chapter 04/myProject/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 04/myProject/Package.swift",
    "chars": 1245,
    "preview": "// swift-tools-version:5.7\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 04/myProject/Public/css/web.css",
    "chars": 35,
    "preview": "#blog h2 {\n    margin: 0.5rem 0;\n}\n"
  },
  {
    "path": "Chapter 04/myProject/Public/js/web.js",
    "chars": 59,
    "preview": "function about() {\n    alert(\"myPage\\n\\nversion 1.0.0\");\n}\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Framework/DatabaseModelInterface.swift",
    "chars": 520,
    "preview": "import Vapor\nimport Fluent\n\npublic protocol DatabaseModelInterface: Fluent.Model\n    where Self.IDValue == UUID\n{    \n  "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Framework/ModuleInterface.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic protocol ModuleInterface {\n    \n    static var identifier: String { get }\n\n    func boot(_ app: App"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift",
    "chars": 466,
    "preview": "import Vapor\n\nstruct ExtendPathMiddleware: AsyncMiddleware {\n\n    func respond(\n        to req: Request,\n        chainin"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/BlogModule.swift",
    "chars": 285,
    "preview": "import Vapor\n\nstruct BlogModule: ModuleInterface {\n\n    let router = BlogRouter()\n\n    func boot(_ app: Application) thr"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/BlogRouter.swift",
    "chars": 284,
    "preview": "import Vapor\n\nstruct BlogRouter: RouteCollection {\n    \n    let controller = BlogFrontendController()\n    \n    func boot"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift",
    "chars": 1836,
    "preview": "import Vapor\nimport Fluent\n\nstruct BlogFrontendController {\n    \n    func blogView(req: Request) async throws -> Respons"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift",
    "chars": 2682,
    "preview": "import Foundation\nimport Fluent\n\nenum BlogMigrations {\n    \n    struct v1: AsyncMigration {\n        \n        func prepar"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift",
    "chars": 581,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogCategoryModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n\n "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift",
    "chars": 1515,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogPostModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n    \n "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Objects/Blog.swift",
    "chars": 86,
    "preview": "enum Blog {\n    \n    enum Post {\n        \n    }\n\n    enum Category {\n        \n    }\n}\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift",
    "chars": 132,
    "preview": "import Foundation\n\nextension Blog.Category {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: Str"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift",
    "chars": 488,
    "preview": "import Foundation\n\nextension Blog.Post {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: String\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift",
    "chars": 58,
    "preview": "struct BlogPostContext {\n    let post: Blog.Post.Detail\n}\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift",
    "chars": 127,
    "preview": "struct BlogPostsContext {\n    let icon: String\n    let title: String\n    let message: String\n    let posts: [Blog.Post.L"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift",
    "chars": 1128,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostTemplate: TemplateRepresentable {\n\n    var context: BlogPostContext\n    \n "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift",
    "chars": 1134,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostsTemplate: TemplateRepresentable {\n    \n    var context: BlogPostsContext\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift",
    "chars": 812,
    "preview": "import Vapor\n\nstruct WebFrontendController {\n    \n    func homeView(req: Request) throws -> Response {\n        let ctx ="
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift",
    "chars": 151,
    "preview": "struct WebHomeContext {\n    let icon: String\n    let title: String\n    let message: String\n    let paragraphs: [String]\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift",
    "chars": 153,
    "preview": "public struct WebIndexContext {\n    \n    public let title: String\n    \n    public init(\n        title: String\n    ) {\n  "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift",
    "chars": 223,
    "preview": "public struct WebLinkContext {\n    \n    public let label: String\n    public let url: String\n    \n    public init(\n      "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift",
    "chars": 836,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebHomeTemplate: TemplateRepresentable {\n\n    var context: WebHomeContext\n    \n   "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift",
    "chars": 3947,
    "preview": "import Vapor\nimport SwiftHtml\nimport SwiftSvg\n\nextension Svg {\n    \n    static func menuIcon() -> Svg {\n        Svg {\n  "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift",
    "chars": 342,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebLinkTemplate: TemplateRepresentable {\n\n    var context: WebLinkContext\n    \n   "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/WebModule.swift",
    "chars": 176,
    "preview": "import Vapor\n\nstruct WebModule: ModuleInterface {\n\n    let router = WebRouter()\n\n    func boot(_ app: Application) throw"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Modules/Web/WebRouter.swift",
    "chars": 230,
    "preview": "import Vapor\n\nstruct WebRouter: RouteCollection {\n    \n    let frontendController = WebFrontendController()\n\n    func bo"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Template/Request+Template.swift",
    "chars": 95,
    "preview": "import Vapor\n\npublic extension Request {\n    var templates: TemplateRenderer { .init(self) }\n}\n"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Template/TemplateRenderer.swift",
    "chars": 687,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct TemplateRenderer {\n    \n    var req: Request\n    \n    init(_ req: Request) "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/Template/TemplateRepresentable.swift",
    "chars": 133,
    "preview": "import Vapor\nimport SwiftSgml\n\npublic protocol TemplateRepresentable {\n    \n    @TagBuilder\n    func render(_ req: Reque"
  },
  {
    "path": "Chapter 04/myProject/Sources/App/configure.swift",
    "chars": 603,
    "preview": "import Vapor\nimport Fluent\nimport FluentSQLiteDriver\n\npublic func configure(\n    _ app: Application\n) throws {\n\n    let "
  },
  {
    "path": "Chapter 04/myProject/Sources/App/routes.swift",
    "chars": 361,
    "preview": "import Vapor\nimport SwiftHtml\n\nfunc routes(_ app: Application) throws {\n\n    app.routes.get { req -> Response in\n       "
  },
  {
    "path": "Chapter 04/myProject/Sources/Run/main.swift",
    "chars": 185,
    "preview": "import App\nimport Vapor\n\nvar env = try Environment.detect()\ntry LoggingSystem.bootstrap(from: &env)\nlet app = Applicatio"
  },
  {
    "path": "Chapter 04/myProject/Tests/AppTests/AppTests.swift",
    "chars": 393,
    "preview": "@testable import App\nimport XCTVapor\n\nfinal class AppTests: XCTestCase {\n    func testHelloWorld() throws {\n        let "
  },
  {
    "path": "Chapter 04/myProject/docker-compose.yml",
    "chars": 854,
    "preview": "# Docker Compose file for Vapor\n#\n# Install Docker on your system to run and test\n# your Vapor app in a production-like "
  },
  {
    "path": "Chapter 05/myProject/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 05/myProject/Package.swift",
    "chars": 1245,
    "preview": "// swift-tools-version:5.7\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 05/myProject/Public/css/web.css",
    "chars": 35,
    "preview": "#blog h2 {\n    margin: 0.5rem 0;\n}\n"
  },
  {
    "path": "Chapter 05/myProject/Public/js/web.js",
    "chars": 59,
    "preview": "function about() {\n    alert(\"myPage\\n\\nversion 1.0.0\");\n}\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Framework/AuthenticatedUser.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic struct AuthenticatedUser {\n\n    public let id: UUID\n    public let email: String\n    \n    public in"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Framework/DatabaseModelInterface.swift",
    "chars": 520,
    "preview": "import Vapor\nimport Fluent\n\npublic protocol DatabaseModelInterface: Fluent.Model\n    where Self.IDValue == UUID\n{    \n  "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Framework/ModuleInterface.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic protocol ModuleInterface {\n    \n    static var identifier: String { get }\n\n    func boot(_ app: App"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift",
    "chars": 466,
    "preview": "import Vapor\n\nstruct ExtendPathMiddleware: AsyncMiddleware {\n\n    func respond(\n        to req: Request,\n        chainin"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/BlogModule.swift",
    "chars": 285,
    "preview": "import Vapor\n\nstruct BlogModule: ModuleInterface {\n\n    let router = BlogRouter()\n\n    func boot(_ app: Application) thr"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/BlogRouter.swift",
    "chars": 284,
    "preview": "import Vapor\n\nstruct BlogRouter: RouteCollection {\n    \n    let controller = BlogFrontendController()\n    \n    func boot"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift",
    "chars": 1836,
    "preview": "import Vapor\nimport Fluent\n\nstruct BlogFrontendController {\n    \n    func blogView(req: Request) async throws -> Respons"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift",
    "chars": 2682,
    "preview": "import Foundation\nimport Fluent\n\nenum BlogMigrations {\n    \n    struct v1: AsyncMigration {\n        \n        func prepar"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift",
    "chars": 581,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogCategoryModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n\n "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift",
    "chars": 1515,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogPostModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n    \n "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Objects/Blog.swift",
    "chars": 86,
    "preview": "enum Blog {\n    \n    enum Post {\n        \n    }\n\n    enum Category {\n        \n    }\n}\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift",
    "chars": 132,
    "preview": "import Foundation\n\nextension Blog.Category {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: Str"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift",
    "chars": 488,
    "preview": "import Foundation\n\nextension Blog.Post {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: String\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift",
    "chars": 58,
    "preview": "struct BlogPostContext {\n    let post: Blog.Post.Detail\n}\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift",
    "chars": 127,
    "preview": "struct BlogPostsContext {\n    let icon: String\n    let title: String\n    let message: String\n    let posts: [Blog.Post.L"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift",
    "chars": 1128,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostTemplate: TemplateRepresentable {\n\n    var context: BlogPostContext\n    \n "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift",
    "chars": 1134,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostsTemplate: TemplateRepresentable {\n    \n    var context: BlogPostsContext\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Authenticators/UserCredentialsAuthenticator.swift",
    "chars": 952,
    "preview": "import Vapor\nimport Fluent\n\nstruct UserCredentialsAuthenticator: AsyncCredentialsAuthenticator {\n    \n    struct Credent"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Authenticators/UserSessionAuthenticator.swift",
    "chars": 541,
    "preview": "import Vapor\nimport Fluent\n\nstruct UserSessionAuthenticator: AsyncSessionAuthenticator {\n    typealias User = Authentica"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Controllers/UserFrontendController.swift",
    "chars": 1626,
    "preview": "import Vapor\n\nstruct UserFrontendController {\n    \n    private struct Input: Decodable {\n        let email: String?\n    "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Database/Migrations/UserMigrations.swift",
    "chars": 1135,
    "preview": "import Vapor\nimport Fluent\n\nenum UserMigrations {\n    \n    struct v1: AsyncMigration {\n        \n        func prepare(on "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Database/Models/UserAccountModel.swift",
    "chars": 659,
    "preview": "import Vapor\nimport Fluent\n\nfinal class UserAccountModel: DatabaseModelInterface {\n    typealias Module = UserModule\n   "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Templates/Contexts/UserLoginContext.swift",
    "chars": 533,
    "preview": "struct UserLoginContext {\n    \n    let icon: String\n    let title: String\n    let message: String\n    let email: String?"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/Templates/Html/UserLoginTemplate.swift",
    "chars": 2015,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct UserLoginTemplate: TemplateRepresentable {\n    \n    var context: UserLoginContext\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/UserModule.swift",
    "chars": 349,
    "preview": "import Vapor\n\nstruct UserModule: ModuleInterface {\n\n    let router = UserRouter()\n\n    func boot(_ app: Application) thr"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/User/UserRouter.swift",
    "chars": 496,
    "preview": "import Vapor\n\nstruct UserRouter: RouteCollection {\n    \n    let frontendController = UserFrontendController()\n    \n    f"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift",
    "chars": 812,
    "preview": "import Vapor\n\nstruct WebFrontendController {\n    \n    func homeView(req: Request) throws -> Response {\n        let ctx ="
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift",
    "chars": 151,
    "preview": "struct WebHomeContext {\n    let icon: String\n    let title: String\n    let message: String\n    let paragraphs: [String]\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift",
    "chars": 153,
    "preview": "public struct WebIndexContext {\n    \n    public let title: String\n    \n    public init(\n        title: String\n    ) {\n  "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift",
    "chars": 223,
    "preview": "public struct WebLinkContext {\n    \n    public let label: String\n    public let url: String\n    \n    public init(\n      "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift",
    "chars": 836,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebHomeTemplate: TemplateRepresentable {\n\n    var context: WebHomeContext\n    \n   "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift",
    "chars": 4379,
    "preview": "import Vapor\nimport SwiftHtml\nimport SwiftSvg\n\nextension Svg {\n    \n    static func menuIcon() -> Svg {\n        Svg {\n  "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift",
    "chars": 342,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebLinkTemplate: TemplateRepresentable {\n\n    var context: WebLinkContext\n    \n   "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/WebModule.swift",
    "chars": 176,
    "preview": "import Vapor\n\nstruct WebModule: ModuleInterface {\n\n    let router = WebRouter()\n\n    func boot(_ app: Application) throw"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Modules/Web/WebRouter.swift",
    "chars": 230,
    "preview": "import Vapor\n\nstruct WebRouter: RouteCollection {\n    \n    let frontendController = WebFrontendController()\n\n    func bo"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Template/Request+Template.swift",
    "chars": 95,
    "preview": "import Vapor\n\npublic extension Request {\n    var templates: TemplateRenderer { .init(self) }\n}\n"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Template/TemplateRenderer.swift",
    "chars": 687,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct TemplateRenderer {\n    \n    var req: Request\n    \n    init(_ req: Request) "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/Template/TemplateRepresentable.swift",
    "chars": 133,
    "preview": "import Vapor\nimport SwiftSgml\n\npublic protocol TemplateRepresentable {\n    \n    @TagBuilder\n    func render(_ req: Reque"
  },
  {
    "path": "Chapter 05/myProject/Sources/App/configure.swift",
    "chars": 752,
    "preview": "import Vapor\nimport Fluent\nimport FluentSQLiteDriver\n\npublic func configure(\n    _ app: Application\n) throws {\n\n    let "
  },
  {
    "path": "Chapter 05/myProject/Sources/App/routes.swift",
    "chars": 361,
    "preview": "import Vapor\nimport SwiftHtml\n\nfunc routes(_ app: Application) throws {\n\n    app.routes.get { req -> Response in\n       "
  },
  {
    "path": "Chapter 05/myProject/Sources/Run/main.swift",
    "chars": 185,
    "preview": "import App\nimport Vapor\n\nvar env = try Environment.detect()\ntry LoggingSystem.bootstrap(from: &env)\nlet app = Applicatio"
  },
  {
    "path": "Chapter 05/myProject/Tests/AppTests/AppTests.swift",
    "chars": 393,
    "preview": "@testable import App\nimport XCTVapor\n\nfinal class AppTests: XCTestCase {\n    func testHelloWorld() throws {\n        let "
  },
  {
    "path": "Chapter 05/myProject/docker-compose.yml",
    "chars": 854,
    "preview": "# Docker Compose file for Vapor\n#\n# Install Docker on your system to run and test\n# your Vapor app in a production-like "
  },
  {
    "path": "Chapter 06/myProject/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 06/myProject/Package.swift",
    "chars": 1245,
    "preview": "// swift-tools-version:5.7\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 06/myProject/Public/css/web.css",
    "chars": 35,
    "preview": "#blog h2 {\n    margin: 0.5rem 0;\n}\n"
  },
  {
    "path": "Chapter 06/myProject/Public/js/web.js",
    "chars": 59,
    "preview": "function about() {\n    alert(\"myPage\\n\\nversion 1.0.0\");\n}\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/AuthenticatedUser.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic struct AuthenticatedUser {\n\n    public let id: UUID\n    public let email: String\n    \n    public in"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/DatabaseModelInterface.swift",
    "chars": 520,
    "preview": "import Vapor\nimport Fluent\n\npublic protocol DatabaseModelInterface: Fluent.Model\n    where Self.IDValue == UUID\n{    \n  "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/AbstractForm.swift",
    "chars": 1794,
    "preview": "import Vapor\n\nopen class AbstractForm: FormComponent {\n    \n    open var action: FormAction\n    open var fields: [FormCo"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/AbstractFormField.swift",
    "chars": 1272,
    "preview": "import Vapor\n\nopen class AbstractFormField<\n    Input: Decodable,\n    Output: TemplateRepresentable\n>: FormComponent {\n "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Fields/InputField.swift",
    "chars": 328,
    "preview": "public final class InputField: AbstractFormField<\n    String,\n    InputFieldTemplate\n> {\n\n    public convenience init(_ "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/FormAction.swift",
    "chars": 394,
    "preview": "import SwiftHtml\n\npublic struct FormAction {\n    \n    public var method: SwiftHtml.Method\n    public var url: String?\n  "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/FormComponent.swift",
    "chars": 370,
    "preview": "import Vapor\n\npublic protocol FormComponent {\n    \n    func load(req: Request) async throws\n    func process(req: Reques"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/FormComponentBuilder.swift",
    "chars": 183,
    "preview": "@resultBuilder\npublic enum FormComponentBuilder {\n    \n    public static func buildBlock(\n        _ components: FormComp"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/FormContext.swift",
    "chars": 172,
    "preview": "public struct FormContext {\n    public var action: FormAction\n    public var fields: [TemplateRepresentable]\n    public "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/InputFieldContext.swift",
    "chars": 672,
    "preview": "import SwiftHtml\n\npublic struct InputFieldContext {\n    \n    public let key: String\n    public var label: LabelContext\n "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Contexts/LabelContext.swift",
    "chars": 407,
    "preview": "public struct LabelContext {\n    \n    public var key: String\n    public var title: String?\n    public var required: Bool"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/FormTemplate.swift",
    "chars": 912,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct FormTemplate: TemplateRepresentable {\n    \n    var context: FormContext\n   "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/InputFieldTemplate.swift",
    "chars": 668,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct InputFieldTemplate: TemplateRepresentable {\n\n    public var context: InputF"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/Form/Templates/Html/LabelTemplate.swift",
    "chars": 630,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct LabelTemplate: TemplateRepresentable {\n\n    var context: LabelContext\n\n    "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Framework/ModuleInterface.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic protocol ModuleInterface {\n    \n    static var identifier: String { get }\n\n    func boot(_ app: App"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Middlewares/ExtendPathMiddleware.swift",
    "chars": 466,
    "preview": "import Vapor\n\nstruct ExtendPathMiddleware: AsyncMiddleware {\n\n    func respond(\n        to req: Request,\n        chainin"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/BlogModule.swift",
    "chars": 285,
    "preview": "import Vapor\n\nstruct BlogModule: ModuleInterface {\n\n    let router = BlogRouter()\n\n    func boot(_ app: Application) thr"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/BlogRouter.swift",
    "chars": 284,
    "preview": "import Vapor\n\nstruct BlogRouter: RouteCollection {\n    \n    let controller = BlogFrontendController()\n    \n    func boot"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Controllers/BlogFrontendController.swift",
    "chars": 1836,
    "preview": "import Vapor\nimport Fluent\n\nstruct BlogFrontendController {\n    \n    func blogView(req: Request) async throws -> Respons"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Database/Migrations/BlogMigrations.swift",
    "chars": 2682,
    "preview": "import Foundation\nimport Fluent\n\nenum BlogMigrations {\n    \n    struct v1: AsyncMigration {\n        \n        func prepar"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Database/Models/BlogCategoryModel.swift",
    "chars": 581,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogCategoryModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n\n "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Database/Models/BlogPostModel.swift",
    "chars": 1515,
    "preview": "import Vapor\nimport Fluent\n\nfinal class BlogPostModel: DatabaseModelInterface {\n    typealias Module = BlogModule\n    \n "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Objects/Blog.swift",
    "chars": 86,
    "preview": "enum Blog {\n    \n    enum Post {\n        \n    }\n\n    enum Category {\n        \n    }\n}\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Objects/BlogCategory.swift",
    "chars": 132,
    "preview": "import Foundation\n\nextension Blog.Category {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: Str"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Objects/BlogPost.swift",
    "chars": 488,
    "preview": "import Foundation\n\nextension Blog.Post {\n    \n    struct List: Codable {\n        let id: UUID\n        let title: String\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostContext.swift",
    "chars": 58,
    "preview": "struct BlogPostContext {\n    let post: Blog.Post.Detail\n}\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Contexts/BlogPostsContext.swift",
    "chars": 127,
    "preview": "struct BlogPostsContext {\n    let icon: String\n    let title: String\n    let message: String\n    let posts: [Blog.Post.L"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostTemplate.swift",
    "chars": 1128,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostTemplate: TemplateRepresentable {\n\n    var context: BlogPostContext\n    \n "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Blog/Templates/Html/BlogPostsTemplate.swift",
    "chars": 1134,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct BlogPostsTemplate: TemplateRepresentable {\n    \n    var context: BlogPostsContext\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Authenticators/UserCredentialsAuthenticator.swift",
    "chars": 952,
    "preview": "import Vapor\nimport Fluent\n\nstruct UserCredentialsAuthenticator: AsyncCredentialsAuthenticator {\n    \n    struct Credent"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Authenticators/UserSessionAuthenticator.swift",
    "chars": 541,
    "preview": "import Vapor\nimport Fluent\n\nstruct UserSessionAuthenticator: AsyncSessionAuthenticator {\n    typealias User = Authentica"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Controllers/UserFrontendController.swift",
    "chars": 1327,
    "preview": "import Vapor\n\nstruct UserFrontendController {\n    \n    private func renderSignInView(\n        _ req: Request,\n        _ "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Database/Migrations/UserMigrations.swift",
    "chars": 1135,
    "preview": "import Vapor\nimport Fluent\n\nenum UserMigrations {\n    \n    struct v1: AsyncMigration {\n        \n        func prepare(on "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Database/Models/UserAccountModel.swift",
    "chars": 659,
    "preview": "import Vapor\nimport Fluent\n\nfinal class UserAccountModel: DatabaseModelInterface {\n    typealias Module = UserModule\n   "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Forms/UserLoginForm.swift",
    "chars": 723,
    "preview": "import Vapor\n\nfinal class UserLoginForm: AbstractForm {\n    \n    public convenience init() {\n        self.init(\n        "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Templates/Contexts/UserLoginContext.swift",
    "chars": 379,
    "preview": "struct UserLoginContext {\n    \n    let icon: String\n    let title: String\n    let message: String\n    let form: Template"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/Templates/Html/UserLoginTemplate.swift",
    "chars": 725,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct UserLoginTemplate: TemplateRepresentable {\n\n    var context: UserLoginContext\n    "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/UserModule.swift",
    "chars": 349,
    "preview": "import Vapor\n\nstruct UserModule: ModuleInterface {\n\n    let router = UserRouter()\n\n    func boot(_ app: Application) thr"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/User/UserRouter.swift",
    "chars": 496,
    "preview": "import Vapor\n\nstruct UserRouter: RouteCollection {\n    \n    let frontendController = UserFrontendController()\n    \n    f"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Controllers/WebFrontendController.swift",
    "chars": 812,
    "preview": "import Vapor\n\nstruct WebFrontendController {\n    \n    func homeView(req: Request) throws -> Response {\n        let ctx ="
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebHomeContext.swift",
    "chars": 151,
    "preview": "struct WebHomeContext {\n    let icon: String\n    let title: String\n    let message: String\n    let paragraphs: [String]\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebIndexContext.swift",
    "chars": 153,
    "preview": "public struct WebIndexContext {\n    \n    public let title: String\n    \n    public init(\n        title: String\n    ) {\n  "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Contexts/WebLinkContext.swift",
    "chars": 223,
    "preview": "public struct WebLinkContext {\n    \n    public let label: String\n    public let url: String\n    \n    public init(\n      "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebHomeTemplate.swift",
    "chars": 836,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebHomeTemplate: TemplateRepresentable {\n\n    var context: WebHomeContext\n    \n   "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebIndexTemplate.swift",
    "chars": 4379,
    "preview": "import Vapor\nimport SwiftHtml\nimport SwiftSvg\n\nextension Svg {\n    \n    static func menuIcon() -> Svg {\n        Svg {\n  "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/Templates/Html/WebLinkTemplate.swift",
    "chars": 342,
    "preview": "import Vapor\nimport SwiftHtml\n\nstruct WebLinkTemplate: TemplateRepresentable {\n\n    var context: WebLinkContext\n    \n   "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/WebModule.swift",
    "chars": 176,
    "preview": "import Vapor\n\nstruct WebModule: ModuleInterface {\n\n    let router = WebRouter()\n\n    func boot(_ app: Application) throw"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Modules/Web/WebRouter.swift",
    "chars": 230,
    "preview": "import Vapor\n\nstruct WebRouter: RouteCollection {\n    \n    let frontendController = WebFrontendController()\n\n    func bo"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Template/Request+Template.swift",
    "chars": 95,
    "preview": "import Vapor\n\npublic extension Request {\n    var templates: TemplateRenderer { .init(self) }\n}\n"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Template/TemplateRenderer.swift",
    "chars": 687,
    "preview": "import Vapor\nimport SwiftHtml\n\npublic struct TemplateRenderer {\n    \n    var req: Request\n    \n    init(_ req: Request) "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/Template/TemplateRepresentable.swift",
    "chars": 133,
    "preview": "import Vapor\nimport SwiftSgml\n\npublic protocol TemplateRepresentable {\n    \n    @TagBuilder\n    func render(_ req: Reque"
  },
  {
    "path": "Chapter 06/myProject/Sources/App/configure.swift",
    "chars": 752,
    "preview": "import Vapor\nimport Fluent\nimport FluentSQLiteDriver\n\npublic func configure(\n    _ app: Application\n) throws {\n\n    let "
  },
  {
    "path": "Chapter 06/myProject/Sources/App/routes.swift",
    "chars": 361,
    "preview": "import Vapor\nimport SwiftHtml\n\nfunc routes(_ app: Application) throws {\n\n    app.routes.get { req -> Response in\n       "
  },
  {
    "path": "Chapter 06/myProject/Sources/Run/main.swift",
    "chars": 185,
    "preview": "import App\nimport Vapor\n\nvar env = try Environment.detect()\ntry LoggingSystem.bootstrap(from: &env)\nlet app = Applicatio"
  },
  {
    "path": "Chapter 06/myProject/Tests/AppTests/AppTests.swift",
    "chars": 393,
    "preview": "@testable import App\nimport XCTVapor\n\nfinal class AppTests: XCTestCase {\n    func testHelloWorld() throws {\n        let "
  },
  {
    "path": "Chapter 06/myProject/docker-compose.yml",
    "chars": 854,
    "preview": "# Docker Compose file for Vapor\n#\n# Install Docker on your system to run and test\n# your Vapor app in a production-like "
  },
  {
    "path": "Chapter 07/myProject/Dockerfile",
    "chars": 3336,
    "preview": "# ================================\n# Build image\n# ================================\nFROM swift:6.0-noble AS build\n\n# Ins"
  },
  {
    "path": "Chapter 07/myProject/Package.swift",
    "chars": 1245,
    "preview": "// swift-tools-version:5.7\nimport PackageDescription\n\nlet package = Package(\n    name: \"myProject\",\n    platforms: [\n   "
  },
  {
    "path": "Chapter 07/myProject/Public/css/web.css",
    "chars": 35,
    "preview": "#blog h2 {\n    margin: 0.5rem 0;\n}\n"
  },
  {
    "path": "Chapter 07/myProject/Public/js/web.js",
    "chars": 59,
    "preview": "function about() {\n    alert(\"myPage\\n\\nversion 1.0.0\");\n}\n"
  },
  {
    "path": "Chapter 07/myProject/Sources/App/Framework/AuthenticatedUser.swift",
    "chars": 323,
    "preview": "import Vapor\n\npublic struct AuthenticatedUser {\n\n    public let id: UUID\n    public let email: String\n    \n    public in"
  },
  {
    "path": "Chapter 07/myProject/Sources/App/Framework/DatabaseModelInterface.swift",
    "chars": 520,
    "preview": "import Vapor\nimport Fluent\n\npublic protocol DatabaseModelInterface: Fluent.Model\n    where Self.IDValue == UUID\n{    \n  "
  },
  {
    "path": "Chapter 07/myProject/Sources/App/Framework/Form/AbstractForm.swift",
    "chars": 1794,
    "preview": "import Vapor\n\nopen class AbstractForm: FormComponent {\n    \n    open var action: FormAction\n    open var fields: [FormCo"
  },
  {
    "path": "Chapter 07/myProject/Sources/App/Framework/Form/AbstractFormField.swift",
    "chars": 2776,
    "preview": "import Vapor\n\nopen class AbstractFormField<\n    Input: Decodable,\n    Output: TemplateRepresentable\n>: FormComponent {\n "
  },
  {
    "path": "Chapter 07/myProject/Sources/App/Framework/Form/Fields/InputField.swift",
    "chars": 328,
    "preview": "public final class InputField: AbstractFormField<\n    String,\n    InputFieldTemplate\n> {\n\n    public convenience init(_ "
  }
]

// ... and 1355 more files (download for full content)

About this extraction

This page contains the full source code of the tib/practical-server-side-swift GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1555 files (1.3 MB), approximately 357.6k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!